@boyingliu01/xp-gate 0.20.1 → 0.20.2
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/gate-3.sh +51 -3
- package/gate-4.sh +32 -1
- package/hooks/gate-3.sh +51 -3
- package/hooks/gate-4.sh +32 -1
- package/hooks/pre-commit +275 -36
- 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 +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/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 +2 -2
- 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 +2 -2
- package/plugins/claude-code/skills/test-driven-development/SKILL.md +1 -0
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
- 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 +2 -2
- 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 +2 -2
- package/plugins/dsh/skills/test-driven-development/SKILL.md +1 -0
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +2 -2
- 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 +2 -2
- 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 +2 -2
- package/plugins/opencode/skills/test-driven-development/SKILL.md +1 -0
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
- 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 +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/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 +2 -2
- 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 +2 -2
- package/skills/test-driven-development/SKILL.md +1 -0
- package/skills/test-specification-alignment/AGENTS.md +2 -2
- package/skills/test-specification-alignment/SKILL.md +1 -0
- package/skills/to-issues/SKILL.md +1 -0
package/hooks/gate-3.sh
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# shellcheck shell=bash
|
|
1
2
|
# ============================================================================
|
|
2
3
|
# GATE 3: Cyclomatic Complexity Check
|
|
3
4
|
# Uses lizard - checks cyclomatic complexity of changed source files
|
|
@@ -17,7 +18,45 @@ elif [ "$PROJECT_LANG" = "powershell" ]; then
|
|
|
17
18
|
GATE_3_STATUS="SKIP"
|
|
18
19
|
|
|
19
20
|
else
|
|
20
|
-
|
|
21
|
+
# S2 (#507): three-level CCN threshold override --
|
|
22
|
+
# .xp-gate/ccn-threshold (positive integer) > XP_GATE_CCN_THRESHOLD > 5.
|
|
23
|
+
# Invalid values WARN on stderr and fall back to the default; the resolved
|
|
24
|
+
# value reaches the gate-3 audit record via the --detail argument below.
|
|
25
|
+
# Implemented with bash builtins only (no tr/grep spawns): every process
|
|
26
|
+
# spawn is expensive on Windows under AV filter drivers.
|
|
27
|
+
resolve_ccn_threshold() {
|
|
28
|
+
local default=5 val
|
|
29
|
+
if [ -f ".xp-gate/ccn-threshold" ]; then
|
|
30
|
+
val=$(<".xp-gate/ccn-threshold")
|
|
31
|
+
val="${val//[[:space:]]/}"
|
|
32
|
+
case "$val" in
|
|
33
|
+
''|*[!0-9]*|0)
|
|
34
|
+
echo "WARN - invalid .xp-gate/ccn-threshold value '${val}' (expected a positive integer), falling back to ${default}" >&2
|
|
35
|
+
;;
|
|
36
|
+
*)
|
|
37
|
+
echo "$val"
|
|
38
|
+
return 0
|
|
39
|
+
;;
|
|
40
|
+
esac
|
|
41
|
+
echo "$default"
|
|
42
|
+
return 0
|
|
43
|
+
fi
|
|
44
|
+
val=${XP_GATE_CCN_THRESHOLD:-}
|
|
45
|
+
if [ -n "$val" ]; then
|
|
46
|
+
case "$val" in
|
|
47
|
+
''|*[!0-9]*|0)
|
|
48
|
+
echo "WARN - invalid XP_GATE_CCN_THRESHOLD value '${val}' (expected a positive integer), falling back to ${default}" >&2
|
|
49
|
+
;;
|
|
50
|
+
*)
|
|
51
|
+
echo "$val"
|
|
52
|
+
return 0
|
|
53
|
+
;;
|
|
54
|
+
esac
|
|
55
|
+
fi
|
|
56
|
+
echo "$default"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
CCN_THRESHOLD=$(resolve_ccn_threshold)
|
|
21
60
|
|
|
22
61
|
# Check lizard availability
|
|
23
62
|
LIZARD_CMD=""
|
|
@@ -37,7 +76,16 @@ else
|
|
|
37
76
|
echo "Checking complexity for source files..."
|
|
38
77
|
|
|
39
78
|
# Run lizard with CCN threshold
|
|
40
|
-
|
|
79
|
+
# One path per line, into an array. Unquoted $CC_FILES relied on word
|
|
80
|
+
# splitting, which also splits any path containing a space -- handing
|
|
81
|
+
# lizard two bogus paths and checking neither (#457 defect class, same
|
|
82
|
+
# fix as gate-4's PRINCIPLES_ARGS). Quoting the scalar would instead
|
|
83
|
+
# collapse the list into one argument. An array does both.
|
|
84
|
+
CC_FILES_ARGS=()
|
|
85
|
+
while IFS= read -r _cc_file; do
|
|
86
|
+
[ -n "$_cc_file" ] && CC_FILES_ARGS+=("$_cc_file")
|
|
87
|
+
done <<< "$CC_FILES"
|
|
88
|
+
CC_OUTPUT=$("$LIZARD_PATH" -C "$CCN_THRESHOLD" ${CC_FILES_ARGS[@]+"${CC_FILES_ARGS[@]}"} 2>&1 || true)
|
|
41
89
|
|
|
42
90
|
# Parse warning count from the summary table: "Warning cnt 8"
|
|
43
91
|
# Use anchored grep to avoid matching lizard table headers (e.g. "Rt" column)
|
|
@@ -65,4 +113,4 @@ else
|
|
|
65
113
|
GATE_3_STATUS="WARN"
|
|
66
114
|
fi
|
|
67
115
|
fi
|
|
68
|
-
record_gate_audit "gate-3" "complexity" "$GATE_3_STATUS" "${CC_WARNINGS:-0}" "$GATE_3_START"
|
|
116
|
+
record_gate_audit "gate-3" "complexity" "$GATE_3_STATUS" "${CC_WARNINGS:-0}" "$GATE_3_START" "ccn_threshold=${CCN_THRESHOLD:-5}"
|
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
|
|
@@ -726,7 +741,7 @@ CHANGEDFILELIST
|
|
|
726
741
|
# Only used when extension-based detection found nothing
|
|
727
742
|
if [ -z "$PROJECT_LANGS" ]; then
|
|
728
743
|
# 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)
|
|
744
|
+
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
745
|
if [ -n "$TSCONFIG_SUB" ]; then
|
|
731
746
|
PROJECT_SUBDIR=$(dirname "$TSCONFIG_SUB")
|
|
732
747
|
if [ -f "$PROJECT_SUBDIR/package.json" ] && grep -q "react-native" "$PROJECT_SUBDIR/package.json" 2>/dev/null; then
|
|
@@ -738,7 +753,7 @@ CHANGEDFILELIST
|
|
|
738
753
|
|
|
739
754
|
# Search for Python projects
|
|
740
755
|
if [ -z "$PROJECT_LANGS" ]; then
|
|
741
|
-
PYPROJECT_SUB=$(find . -maxdepth 2 -name "pyproject.toml" -not -path "./.git/*" 2>/dev/null | head -1)
|
|
756
|
+
PYPROJECT_SUB=$(find . -maxdepth 2 -name "pyproject.toml" -not -path "./.git/*" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
742
757
|
if [ -n "$PYPROJECT_SUB" ]; then
|
|
743
758
|
PROJECT_LANGS="python"
|
|
744
759
|
PROJECT_SUBDIR=$(dirname "$PYPROJECT_SUB")
|
|
@@ -747,7 +762,7 @@ CHANGEDFILELIST
|
|
|
747
762
|
|
|
748
763
|
# Search for Go projects
|
|
749
764
|
if [ -z "$PROJECT_LANGS" ]; then
|
|
750
|
-
GOMOD_SUB=$(find . -maxdepth 2 -name "go.mod" -not -path "./.git/*" 2>/dev/null | head -1)
|
|
765
|
+
GOMOD_SUB=$(find . -maxdepth 2 -name "go.mod" -not -path "./.git/*" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
751
766
|
if [ -n "$GOMOD_SUB" ]; then
|
|
752
767
|
PROJECT_LANGS="go"
|
|
753
768
|
PROJECT_SUBDIR=$(dirname "$GOMOD_SUB")
|
|
@@ -756,7 +771,7 @@ CHANGEDFILELIST
|
|
|
756
771
|
|
|
757
772
|
# Search for Flutter/Dart projects
|
|
758
773
|
if [ -z "$PROJECT_LANGS" ]; then
|
|
759
|
-
PUBSPEC_SUB=$(find . -maxdepth 2 -name "pubspec.yaml" -not -path "./.git/*" 2>/dev/null | head -1)
|
|
774
|
+
PUBSPEC_SUB=$(find . -maxdepth 2 -name "pubspec.yaml" -not -path "./.git/*" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
760
775
|
if [ -n "$PUBSPEC_SUB" ]; then
|
|
761
776
|
if grep -q "flutter:" "$PUBSPEC_SUB" 2>/dev/null || [ -f "$(dirname "$PUBSPEC_SUB")/.metadata" ]; then
|
|
762
777
|
PROJECT_LANGS="flutter"
|
|
@@ -769,8 +784,8 @@ CHANGEDFILELIST
|
|
|
769
784
|
|
|
770
785
|
# Search for Java/Kotlin projects
|
|
771
786
|
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)
|
|
787
|
+
POM_SUB=$(find . -maxdepth 2 -name "pom.xml" -not -path "./.git/*" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
788
|
+
GRADLE_SUB=$(find . -maxdepth 2 -name "build.gradle" -not -path "./.git/*" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
774
789
|
if [ -n "$POM_SUB" ] || [ -n "$GRADLE_SUB" ]; then
|
|
775
790
|
MANIFEST_FILE="${POM_SUB:-$GRADLE_SUB}"
|
|
776
791
|
PROJECT_SUBDIR=$(dirname "$MANIFEST_FILE")
|
|
@@ -785,7 +800,7 @@ CHANGEDFILELIST
|
|
|
785
800
|
|
|
786
801
|
# Search for C++ projects
|
|
787
802
|
if [ -z "$PROJECT_LANGS" ]; then
|
|
788
|
-
CMAKE_SUB=$(find . -maxdepth 2 -name "CMakeLists.txt" -not -path "./.git/*" 2>/dev/null | head -1)
|
|
803
|
+
CMAKE_SUB=$(find . -maxdepth 2 -name "CMakeLists.txt" -not -path "./.git/*" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
789
804
|
if [ -n "$CMAKE_SUB" ]; then
|
|
790
805
|
PROJECT_LANGS="cpp"
|
|
791
806
|
PROJECT_SUBDIR=$(dirname "$CMAKE_SUB")
|
|
@@ -794,7 +809,7 @@ CHANGEDFILELIST
|
|
|
794
809
|
|
|
795
810
|
# Search for React Native projects (ios/ + android/ directories)
|
|
796
811
|
if [ -z "$PROJECT_LANGS" ]; then
|
|
797
|
-
RN_SUB=$(find . -maxdepth 2 -type d -name "ios" -not -path "./.git/*" 2>/dev/null | head -1)
|
|
812
|
+
RN_SUB=$(find . -maxdepth 2 -type d -name "ios" -not -path "./.git/*" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
798
813
|
if [ -n "$RN_SUB" ]; then
|
|
799
814
|
PROJECT_SUBDIR=$(dirname "$RN_SUB")
|
|
800
815
|
if [ -d "$PROJECT_SUBDIR/android" ] || [ -f "$PROJECT_SUBDIR/package.json" ]; then
|
|
@@ -805,7 +820,7 @@ CHANGEDFILELIST
|
|
|
805
820
|
|
|
806
821
|
# Search for PowerShell projects
|
|
807
822
|
if [ -z "$PROJECT_LANGS" ]; then
|
|
808
|
-
PS_SUB=$(find . -maxdepth 2 -name "*.ps1" -not -path "./.git/*" 2>/dev/null | head -1)
|
|
823
|
+
PS_SUB=$(find . -maxdepth 2 -name "*.ps1" -not -path "./.git/*" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
809
824
|
if [ -n "$PS_SUB" ]; then
|
|
810
825
|
PROJECT_LANGS="powershell"
|
|
811
826
|
PROJECT_SUBDIR=$(dirname "$PS_SUB")
|
|
@@ -830,7 +845,7 @@ CHANGEDFILELIST
|
|
|
830
845
|
|
|
831
846
|
# ---- Documentation-only fallback ----
|
|
832
847
|
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)
|
|
848
|
+
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
849
|
|
|
835
850
|
if [ "$CODE_FILES" -eq 0 ]; then
|
|
836
851
|
PROJECT_LANGS="documentation-only"
|
|
@@ -1332,7 +1347,66 @@ else
|
|
|
1332
1347
|
echo "✅ PASSED - Basic shell script syntax check."
|
|
1333
1348
|
fi
|
|
1334
1349
|
;;
|
|
1335
|
-
|
|
1350
|
+
|
|
1351
|
+
java)
|
|
1352
|
+
# GATE 1 Java: wc-java-lint with fail-closed verdicts (#507 S1, DR-001).
|
|
1353
|
+
# java.sh run_static_analysis verdict codes:
|
|
1354
|
+
# 0=PASS 1=FAIL(violations) 2=BLOCK(execution error) 3=WARN(non-blocking).
|
|
1355
|
+
# Every non-0/1/2 outcome is scored as WARN -- IN the denominator, so it
|
|
1356
|
+
# forbids an overall PASS. Delphi round-2 A-MAJOR-1: these paths used to
|
|
1357
|
+
# map to SKIP, which sits OUT of the denominator, letting a repo with no
|
|
1358
|
+
# Java engine (or findings suppressed in report mode) report PASS/10.0
|
|
1359
|
+
# while its Java quality was never verified.
|
|
1360
|
+
# The adapter applies the XP_GATE_WC_JAVA_LINT=soft escape valve itself, so
|
|
1361
|
+
# a 2 reaching this branch is hard: BLOCK the commit. Captured to a var
|
|
1362
|
+
# (not piped) so the verdict code survives regardless of output length --
|
|
1363
|
+
# `cmd | head` makes a long report die with SIGPIPE, which would turn a
|
|
1364
|
+
# real verdict into an accident of line count.
|
|
1365
|
+
ADAPTER_FILE=$(resolve_adapter_path "$CURRENT_LANG")
|
|
1366
|
+
if [ -z "$ADAPTER_FILE" ] || ! source "$ADAPTER_FILE" 2>/dev/null; then
|
|
1367
|
+
echo "⚠️ WARN - Java adapter not found (code quality NOT verified)"
|
|
1368
|
+
GATE_1_STATUS="WARN"
|
|
1369
|
+
GATE_1_DETAIL="java: adapter missing"
|
|
1370
|
+
else
|
|
1371
|
+
JAVA_G1_EXIT=0
|
|
1372
|
+
# `|| JAVA_G1_EXIT=$?` (not a bare assignment): under a set -e consumer a
|
|
1373
|
+
# non-zero verdict would abort the host hook before the mapping below.
|
|
1374
|
+
JAVA_G1_OUTPUT=$(run_static_analysis 2>&1) || JAVA_G1_EXIT=$?
|
|
1375
|
+
# SKIP/WARN reasons cross the command-substitution subshell as an
|
|
1376
|
+
# `XP_G1_REASON: ...` stdout line -- an `export` inside the adapter can
|
|
1377
|
+
# NEVER reach this process (#507 Delphi round-1 B-MAJOR-1: verified the
|
|
1378
|
+
# subshell discards the child env). Parse, then strip from display.
|
|
1379
|
+
JAVA_G1_REASON=$(printf '%s\n' "$JAVA_G1_OUTPUT" | sed -n 's/^XP_G1_REASON: //p' | head -1)
|
|
1380
|
+
if [ -n "$JAVA_G1_REASON" ]; then
|
|
1381
|
+
JAVA_G1_OUTPUT=$(printf '%s\n' "$JAVA_G1_OUTPUT" | grep -v '^XP_G1_REASON: ')
|
|
1382
|
+
fi
|
|
1383
|
+
[ -n "$JAVA_G1_OUTPUT" ] && echo "$JAVA_G1_OUTPUT" | head -40
|
|
1384
|
+
case "$JAVA_G1_EXIT" in
|
|
1385
|
+
0)
|
|
1386
|
+
echo "✅ PASSED - Java code quality (wc-java-lint)."
|
|
1387
|
+
;;
|
|
1388
|
+
1)
|
|
1389
|
+
echo "❌ BLOCKED - Java static analysis violations (fix or add suppressions)"
|
|
1390
|
+
exit 1
|
|
1391
|
+
;;
|
|
1392
|
+
2)
|
|
1393
|
+
echo "❌ BLOCKED - wc-java-lint unusable (fail-closed; unset XP_GATE_WC_JAVA_LINT=soft only if you accept the risk)"
|
|
1394
|
+
exit 1
|
|
1395
|
+
;;
|
|
1396
|
+
3)
|
|
1397
|
+
echo "⚠️ WARN - Java code quality NOT verified (engine unavailable or downgraded)"
|
|
1398
|
+
GATE_1_STATUS="WARN"
|
|
1399
|
+
GATE_1_DETAIL="java: ${JAVA_G1_REASON:-unavailable (warn)}"
|
|
1400
|
+
;;
|
|
1401
|
+
*)
|
|
1402
|
+
echo "⚠️ WARN - Java code quality NOT verified (unexpected verdict $JAVA_G1_EXIT)"
|
|
1403
|
+
GATE_1_STATUS="WARN"
|
|
1404
|
+
GATE_1_DETAIL="java: unexpected verdict $JAVA_G1_EXIT"
|
|
1405
|
+
;;
|
|
1406
|
+
esac
|
|
1407
|
+
fi
|
|
1408
|
+
;;
|
|
1409
|
+
|
|
1336
1410
|
*)
|
|
1337
1411
|
# Route to appropriate adapter for other languages
|
|
1338
1412
|
ADAPTER_FILE=$(resolve_adapter_path "$CURRENT_LANG")
|
|
@@ -1367,9 +1441,13 @@ else
|
|
|
1367
1441
|
done
|
|
1368
1442
|
fi
|
|
1369
1443
|
fi # End: if ! any_changed_files_match (Issue #312 skip)
|
|
1370
|
-
GATE_1_STATUS="PASS"
|
|
1444
|
+
GATE_1_STATUS="${GATE_1_STATUS:-PASS}"
|
|
1371
1445
|
GATE_1_TOOL="${PROJECT_LANGS}"
|
|
1372
|
-
|
|
1446
|
+
# The java branch may set GATE_1_STATUS=WARN + a GATE_1_DETAIL warn reason
|
|
1447
|
+
# (engine unavailable / soft or report downgrade); route it into the audit
|
|
1448
|
+
# trail via the S2 detail plumbing so a WARN verdict is diagnosable, not
|
|
1449
|
+
# silent. WARN stays IN the scoring denominator (Delphi round-2 A-MAJOR-1).
|
|
1450
|
+
record_gate_audit "gate-1" "code-quality" "$GATE_1_STATUS" "0" "$GATE_1_START" "${GATE_1_DETAIL:-}"
|
|
1373
1451
|
|
|
1374
1452
|
# ============================================================================
|
|
1375
1453
|
# GATE 2: Duplicate Code Detection (NEW gate)
|
|
@@ -2701,9 +2779,9 @@ else
|
|
|
2701
2779
|
# real source path stays unbudgeted. Exclude it and let the mirroring be
|
|
2702
2780
|
# governed by Mirror Parity in CI, which compares content rather than
|
|
2703
2781
|
# 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' ' ')
|
|
2782
|
+
MIRROR_EXCLUDE="$MIRROR_PATH_FILTER"
|
|
2783
|
+
NEW_FILES=$(git diff --cached --name-only --diff-filter=A | grep -v "$MIRROR_EXCLUDE" | grep -v "$DASHBOARD_PATH_FILTER" | tr '\n' ' ')
|
|
2784
|
+
MODIFIED_FILES=$(git diff --cached --name-only --diff-filter=M | grep -v "$MIRROR_EXCLUDE" | grep -v "$DASHBOARD_PATH_FILTER" | tr '\n' ' ')
|
|
2707
2785
|
|
|
2708
2786
|
if [ -z "$NEW_FILES" ] && [ -z "$MODIFIED_FILES" ]; then
|
|
2709
2787
|
echo " ✅ No new or modified files to check."
|
|
@@ -2794,9 +2872,73 @@ fi
|
|
|
2794
2872
|
# ============================================================================
|
|
2795
2873
|
# GATE 9: Build Integrity Check (TypeScript compilation + package + imports)
|
|
2796
2874
|
# ============================================================================
|
|
2875
|
+
|
|
2876
|
+
# Java build-integrity dispatch (#507 S5, DR-003). The old gate only knew
|
|
2877
|
+
# TypeScript: a Maven project got a permanent SKIP -- "not a TypeScript
|
|
2878
|
+
# project" -- which is exactly the silent-no-coverage path this sprint
|
|
2879
|
+
# removes. Verdict codes (mirror of the java.sh G1 contract):
|
|
2880
|
+
# 0=PASS 2=BLOCK(compile failed) 3=SKIP+WARN(reason printed)
|
|
2881
|
+
resolve_build_integrity_timeout() {
|
|
2882
|
+
local file=".xp-gate/build-integrity-timeout" val=""
|
|
2883
|
+
if [ -f "$file" ]; then
|
|
2884
|
+
val=$(tr -d '[:space:]' < "$file")
|
|
2885
|
+
fi
|
|
2886
|
+
if [ -z "$val" ]; then
|
|
2887
|
+
echo "180"
|
|
2888
|
+
return 0
|
|
2889
|
+
fi
|
|
2890
|
+
case "$val" in
|
|
2891
|
+
''|*[!0-9]*|0)
|
|
2892
|
+
echo "WARN - invalid $file value '${val}' (expected a positive integer), falling back to 180s" >&2
|
|
2893
|
+
echo "180"
|
|
2894
|
+
;;
|
|
2895
|
+
*) echo "$val" ;;
|
|
2896
|
+
esac
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2899
|
+
run_java_build_integrity() {
|
|
2900
|
+
local timeout_s build_bin
|
|
2901
|
+
# Wrapper first: mvnw pins the project's own Maven version.
|
|
2902
|
+
if [ -x "./mvnw" ]; then
|
|
2903
|
+
build_bin="./mvnw"
|
|
2904
|
+
elif command -v mvn >/dev/null 2>&1; then
|
|
2905
|
+
build_bin="mvn"
|
|
2906
|
+
else
|
|
2907
|
+
echo " ⚠️ Java changes staged but no Maven build tool found (mvnw/mvn)"
|
|
2908
|
+
echo " ⏭️ SKIPPED - Build integrity Java (no build tool)"
|
|
2909
|
+
return 3
|
|
2910
|
+
fi
|
|
2911
|
+
|
|
2912
|
+
timeout_s=$(resolve_build_integrity_timeout)
|
|
2913
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
2914
|
+
timeout "$timeout_s" "$build_bin" -q -DskipTests compile 2>&1 | tail -20
|
|
2915
|
+
else
|
|
2916
|
+
"$build_bin" -q -DskipTests compile 2>&1 | tail -20
|
|
2917
|
+
fi
|
|
2918
|
+
local rc="${PIPESTATUS[0]}"
|
|
2919
|
+
if [ "$rc" -eq 0 ]; then
|
|
2920
|
+
echo " ✅ Java build integrity verified (mvn -q -DskipTests compile)."
|
|
2921
|
+
return 0
|
|
2922
|
+
fi
|
|
2923
|
+
if [ "$rc" -eq 124 ]; then
|
|
2924
|
+
echo " ⏱️ Java build timed out after ${timeout_s}s."
|
|
2925
|
+
echo " ⏭️ SKIPPED - Build integrity Java (timeout)"
|
|
2926
|
+
return 3
|
|
2927
|
+
fi
|
|
2928
|
+
echo " ❌ Java build failed (exit $rc)"
|
|
2929
|
+
return 2
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2797
2932
|
2>&1 echo ""
|
|
2798
2933
|
2>&1 echo "→ Gate 9: Build integrity... "
|
|
2799
2934
|
GATE_9_START=$(gate_start_ms)
|
|
2935
|
+
# Sub-status aggregation (#507 Delphi round-1 B-MAJOR-4): each half records
|
|
2936
|
+
# its own outcome; GATE_9_STATUS is aggregated after both. The previous
|
|
2937
|
+
# single-variable design made a Maven-only repo (TS half never runs, Java
|
|
2938
|
+
# half passes) record an ETERNAL SKIP -- a real compile verification that
|
|
2939
|
+
# never appeared as PASS, contradicting S5's own motivation.
|
|
2940
|
+
GATE_9_TS_STATUS="SKIP"
|
|
2941
|
+
GATE_9_JAVA_STATUS="SKIP"
|
|
2800
2942
|
GATE_9_STATUS="SKIP"
|
|
2801
2943
|
|
|
2802
2944
|
if [ -f "package.json" ] && [ -f "tsconfig.json" ]; then
|
|
@@ -2813,15 +2955,15 @@ if [ -f "package.json" ] && [ -f "tsconfig.json" ]; then
|
|
|
2813
2955
|
CHANGED_FILES_CSV=$(echo "$CHANGED_FILES" | tr ' ' ',')
|
|
2814
2956
|
GATE_9_OUTPUT=$(mktemp)
|
|
2815
2957
|
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
|
-
|
|
2958
|
+
GATE_9_TS_STATUS="PASS"
|
|
2817
2959
|
echo " ✅ Build integrity check passed."
|
|
2818
2960
|
else
|
|
2819
2961
|
GATE_9_EXIT=$?
|
|
2820
2962
|
if [ $GATE_9_EXIT -eq 124 ]; then
|
|
2821
|
-
|
|
2963
|
+
GATE_9_TS_STATUS="SKIP"
|
|
2822
2964
|
echo " ⏱️ Build integrity check timed out (120s). SKIPPED."
|
|
2823
2965
|
else
|
|
2824
|
-
|
|
2966
|
+
GATE_9_TS_STATUS="BLOCK"
|
|
2825
2967
|
cat "$GATE_9_OUTPUT"
|
|
2826
2968
|
echo ""
|
|
2827
2969
|
echo "❌ BLOCKED - Gate 9: Build integrity check failed"
|
|
@@ -2840,6 +2982,52 @@ else
|
|
|
2840
2982
|
echo " ⏭️ SKIPPED - Build integrity (not a TypeScript project)"
|
|
2841
2983
|
fi
|
|
2842
2984
|
|
|
2985
|
+
# Java dispatch (#507 S5): runs when .java files are staged, regardless of
|
|
2986
|
+
# whether the TypeScript half ran. Gradle-only repos SKIP+WARN (gradle
|
|
2987
|
+
# support is backlog; DR-003).
|
|
2988
|
+
if echo "$CHANGED_FILES" | grep -q '\.java$'; then
|
|
2989
|
+
if [ -f "pom.xml" ]; then
|
|
2990
|
+
JAVA_BI_EXIT=0
|
|
2991
|
+
run_java_build_integrity || JAVA_BI_EXIT=$?
|
|
2992
|
+
case "$JAVA_BI_EXIT" in
|
|
2993
|
+
0)
|
|
2994
|
+
GATE_9_JAVA_STATUS="PASS"
|
|
2995
|
+
;;
|
|
2996
|
+
2)
|
|
2997
|
+
GATE_9_JAVA_STATUS="BLOCK"
|
|
2998
|
+
echo ""
|
|
2999
|
+
echo "❌ BLOCKED - Gate 9: Java build integrity check failed"
|
|
3000
|
+
echo "Fix the compile errors before committing."
|
|
3001
|
+
exit 1
|
|
3002
|
+
;;
|
|
3003
|
+
*)
|
|
3004
|
+
GATE_9_JAVA_STATUS="SKIP"
|
|
3005
|
+
echo " ⏭️ SKIPPED - Build integrity Java (see reason above)"
|
|
3006
|
+
;;
|
|
3007
|
+
esac
|
|
3008
|
+
elif [ -f "build.gradle" ] || [ -f "build.gradle.kts" ]; then
|
|
3009
|
+
echo " ⚠️ Java changes staged but this is a Gradle project - build integrity for Gradle is not implemented yet"
|
|
3010
|
+
echo " ⏭️ SKIPPED - Build integrity Java (Gradle backlog, #507 DR-003)"
|
|
3011
|
+
else
|
|
3012
|
+
echo " ℹ️ Java changes staged but no Maven/Gradle build file - skipping Java build integrity"
|
|
3013
|
+
fi
|
|
3014
|
+
fi
|
|
3015
|
+
|
|
3016
|
+
# Aggregate the two halves (#507 Delphi round-1 B-MAJOR-4): PASS when every
|
|
3017
|
+
# half that RAN passed (TS PASS + Java SKIP, or vice versa, both count),
|
|
3018
|
+
# SKIP only when NOTHING ran, BLOCK on any failure (normally unreachable --
|
|
3019
|
+
# failing halves exit 1 inline). This replaces the blind-review MINOR-1
|
|
3020
|
+
# single-variable guard: with explicit sub-statuses a Java PASS no longer
|
|
3021
|
+
# "resurrects" a TS SKIP -- each half keeps its own record and the gate
|
|
3022
|
+
# reflects the union of what was actually verified.
|
|
3023
|
+
GATE_9_STATUS="SKIP"
|
|
3024
|
+
if [ "$GATE_9_TS_STATUS" = "PASS" ] || [ "$GATE_9_JAVA_STATUS" = "PASS" ]; then
|
|
3025
|
+
GATE_9_STATUS="PASS"
|
|
3026
|
+
fi
|
|
3027
|
+
if [ "$GATE_9_TS_STATUS" = "BLOCK" ] || [ "$GATE_9_JAVA_STATUS" = "BLOCK" ]; then
|
|
3028
|
+
GATE_9_STATUS="BLOCK"
|
|
3029
|
+
fi
|
|
3030
|
+
|
|
2843
3031
|
GATE_9_END=$(gate_start_ms)
|
|
2844
3032
|
GATE_9_DURATION_MS=$((GATE_9_END - GATE_9_START))
|
|
2845
3033
|
GATE_9_DURATION=$((GATE_9_DURATION_MS / 1000))
|
|
@@ -2872,7 +3060,7 @@ fi
|
|
|
2872
3060
|
# Validates sprint state consistency (delphi-review APPROVED before BUILD)
|
|
2873
3061
|
# Uses sprint-gate.sh for standalone validation logic
|
|
2874
3062
|
# ============================================================================
|
|
2875
|
-
GATE_11_START=$(
|
|
3063
|
+
GATE_11_START=$(gate_start_ms)
|
|
2876
3064
|
GATE_11_STATUS="PASS"
|
|
2877
3065
|
|
|
2878
3066
|
# BEGIN SPRINT_GATE_SCRIPT RESOLUTION
|
|
@@ -2961,24 +3149,72 @@ generate_quality_report() {
|
|
|
2961
3149
|
local BRANCH
|
|
2962
3150
|
local PASSED_COUNT=0
|
|
2963
3151
|
local TOTAL_GATES=12
|
|
3152
|
+
local SKIP_COUNT=0
|
|
3153
|
+
local WARN_COUNT=0
|
|
3154
|
+
local NONPASS_COUNT=0
|
|
2964
3155
|
|
|
2965
3156
|
COMMIT_HASH=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
|
|
2966
3157
|
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
|
2967
3158
|
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
2968
3159
|
|
|
3160
|
+
# Scoring per #507 S3: SKIP removes a gate from the denominator (a missing
|
|
3161
|
+
# tool must not drag the score down -- that was the "honest but punishing"
|
|
3162
|
+
# old behavior), WARN stays in the denominator but never counts as passed,
|
|
3163
|
+
# and FAIL/BLOCK count as non-passing items like before. NONPASS tracks
|
|
3164
|
+
# non-passing gates INSIDE the effective denominator only -- a skipped gate
|
|
3165
|
+
# is out of scope, not a failure.
|
|
3166
|
+
local gate_status gate_default
|
|
2969
3167
|
for gate in 1 2 3 4 5 6 7 8 9 10 11 12; do
|
|
2970
3168
|
local status_var="GATE_${gate}_STATUS"
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
3169
|
+
# Default mirrors the JSON report rendering below EXACTLY (gates 1-11
|
|
3170
|
+
# PASS, gate 12 WARN): a gate that never assigned a status is "not
|
|
3171
|
+
# applicable" and must render and score identically. A previous scoring
|
|
3172
|
+
# default of FAIL produced self-contradictory reports -- the gate showed
|
|
3173
|
+
# PASS in JSON but scored as FAIL (#507 Delphi round-1 A-MAJOR-2).
|
|
3174
|
+
if [ "$gate" -eq 12 ]; then gate_default="WARN"; else gate_default="PASS"; fi
|
|
3175
|
+
gate_status="${!status_var:-$gate_default}"
|
|
3176
|
+
case "$gate_status" in
|
|
3177
|
+
PASS) PASSED_COUNT=$((PASSED_COUNT + 1)) ;;
|
|
3178
|
+
SKIP) SKIP_COUNT=$((SKIP_COUNT + 1)) ;;
|
|
3179
|
+
WARN) WARN_COUNT=$((WARN_COUNT + 1)); NONPASS_COUNT=$((NONPASS_COUNT + 1)) ;;
|
|
3180
|
+
*) NONPASS_COUNT=$((NONPASS_COUNT + 1)) ;;
|
|
3181
|
+
esac
|
|
2974
3182
|
done
|
|
2975
3183
|
|
|
3184
|
+
local EFFECTIVE_TOTAL=$((TOTAL_GATES - SKIP_COUNT))
|
|
3185
|
+
local VERDICT="PASS"
|
|
3186
|
+
if [ "$EFFECTIVE_TOTAL" -eq 0 ]; then
|
|
3187
|
+
VERDICT="SKIP-ALL" # every gate skipped: never report a fake 10/10
|
|
3188
|
+
elif [ "$NONPASS_COUNT" -gt 0 ]; then
|
|
3189
|
+
if [ "$((NONPASS_COUNT - WARN_COUNT))" -gt 0 ]; then
|
|
3190
|
+
VERDICT="FAIL" # at least one FAIL/BLOCK among the effective gates
|
|
3191
|
+
else
|
|
3192
|
+
VERDICT="WARN" # all effective gates passed or warned; warns forbid PASS
|
|
3193
|
+
fi
|
|
3194
|
+
fi
|
|
3195
|
+
|
|
2976
3196
|
local SCORE=0
|
|
2977
|
-
if
|
|
2978
|
-
SCORE
|
|
3197
|
+
if [ "$EFFECTIVE_TOTAL" -eq 0 ]; then
|
|
3198
|
+
SCORE="N/A"
|
|
2979
3199
|
else
|
|
2980
|
-
|
|
2981
|
-
|
|
3200
|
+
# Single awk path (#507 Delphi round-1 B-MAJOR-2): bc's scale=1 truncates
|
|
3201
|
+
# the DIVISION before multiplying (8/9 -> 0.8, *10 -> 8.0) while awk
|
|
3202
|
+
# rounds the final result (8.9) -- the same commit scored differently on
|
|
3203
|
+
# machines with and without bc. awk(1) is POSIX-guaranteed: one code path,
|
|
3204
|
+
# one score.
|
|
3205
|
+
SCORE=$(awk "BEGIN {printf \"%.1f\", ($PASSED_COUNT / $EFFECTIVE_TOTAL) * 10}")
|
|
3206
|
+
fi
|
|
3207
|
+
local EFFECTIVE_PASS_RATE="N/A"
|
|
3208
|
+
if [ "$EFFECTIVE_TOTAL" -gt 0 ]; then
|
|
3209
|
+
EFFECTIVE_PASS_RATE=$(awk "BEGIN {printf \"%.1f\", ($PASSED_COUNT / $EFFECTIVE_TOTAL) * 100}")
|
|
3210
|
+
fi
|
|
3211
|
+
# JSON bodies need a number or null -- a literal N/A breaks every consumer.
|
|
3212
|
+
local SCORE_JSON="$SCORE"
|
|
3213
|
+
[ "$SCORE_JSON" = "N/A" ] && SCORE_JSON="null"
|
|
3214
|
+
# Same for the pass rate: numeric like `score`, not a quoted string --
|
|
3215
|
+
# mixed types in one object broke consumers (#507 Delphi round-1 B-minor-6).
|
|
3216
|
+
local EFFECTIVE_PASS_RATE_JSON="$EFFECTIVE_PASS_RATE"
|
|
3217
|
+
[ "$EFFECTIVE_PASS_RATE_JSON" = "N/A" ] && EFFECTIVE_PASS_RATE_JSON="null"
|
|
2982
3218
|
|
|
2983
3219
|
local BS_PASSED=0
|
|
2984
3220
|
local BS_BLOCKED=0
|
|
@@ -3018,8 +3254,11 @@ generate_quality_report() {
|
|
|
3018
3254
|
"overall": {
|
|
3019
3255
|
"gatesPassed": $PASSED_COUNT,
|
|
3020
3256
|
"gatesTotal": $TOTAL_GATES,
|
|
3021
|
-
"
|
|
3022
|
-
"
|
|
3257
|
+
"gatesSkipped": $SKIP_COUNT,
|
|
3258
|
+
"gatesEffective": $EFFECTIVE_TOTAL,
|
|
3259
|
+
"effectivePassRate": $EFFECTIVE_PASS_RATE_JSON,
|
|
3260
|
+
"score": $SCORE_JSON,
|
|
3261
|
+
"verdict": "$VERDICT"
|
|
3023
3262
|
},
|
|
3024
3263
|
"gates": {
|
|
3025
3264
|
"gate1_static_analysis": {
|
|
@@ -3114,7 +3353,7 @@ ENDJSON
|
|
|
3114
3353
|
GATES_JSON="${GATES_JSON}\"gate11\":{\"status\":\"${GATE_11_STATUS:-PASS}\",\"name\":\"Sprint Flow\"},"
|
|
3115
3354
|
GATES_JSON="${GATES_JSON}\"gate12\":{\"status\":\"${GATE_12_STATUS:-WARN}\",\"name\":\"File Hygiene\"}}"
|
|
3116
3355
|
|
|
3117
|
-
echo "{\"timestamp\":\"$TIMESTAMP\",\"commit\":\"$COMMIT_HASH\",\"branch\":\"$BRANCH\",\"score\":$
|
|
3356
|
+
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
3357
|
|
|
3119
3358
|
# ── Console output ────────────────────────────────────────────────────────
|
|
3120
3359
|
echo ""
|
|
@@ -3135,7 +3374,7 @@ ENDJSON
|
|
|
3135
3374
|
printf " %-45s %s\n" "Gate 11: Sprint Flow" "${GATE_11_STATUS:-PASS}"
|
|
3136
3375
|
printf " %-45s %s\n" "Gate 12: File Hygiene" "${GATE_12_STATUS:-WARN}"
|
|
3137
3376
|
echo ""
|
|
3138
|
-
echo " Overall Score: $SCORE/10 | $PASSED_COUNT/$TOTAL_GATES gates passed"
|
|
3377
|
+
echo " Overall Score: $SCORE/10 | $PASSED_COUNT/$TOTAL_GATES gates passed (effective: $PASSED_COUNT/$EFFECTIVE_TOTAL, skipped: $SKIP_COUNT, verdict: $VERDICT)"
|
|
3139
3378
|
echo " Branch status: $REPORT_FILE"
|
|
3140
3379
|
echo " Run report: $PERSISTED_REPORT_FILE"
|
|
3141
3380
|
echo " History saved: $HISTORY_FILE"
|