@boyingliu01/xp-gate 0.35.22 → 0.35.24
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/adapter-common.sh +24 -35
- package/adapters/go.sh +6 -11
- package/adapters/kotlin.sh +53 -8
- package/adapters/python.sh +6 -11
- package/gate-13-compiler-strictness.sh +15 -1
- package/hooks/adapter-common.sh +24 -35
- package/hooks/gate-13-compiler-strictness.sh +15 -1
- package/hooks/pre-commit +137 -9
- package/lib/__tests__/doctor-delphi.test.js +3 -3
- package/lib/__tests__/doctor-tui-skill-freshness.test.js +3 -3
- package/lib/__tests__/install-skill.test.js +107 -20
- package/lib/__tests__/uninstall-global-residue.test.js +230 -9
- package/lib/__tests__/update-skill.test.js +179 -55
- package/lib/install-skill.js +18 -3
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/uninstall.js +47 -10
- package/lib/update-skill.js +96 -26
- package/mock-policy/AGENTS.md +3 -3
- package/mutation/AGENTS.md +3 -3
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
- package/principles/AGENTS.md +3 -3
- package/scripts/delphi-external-review.cjs +68 -1
- package/skills/delphi-review/AGENTS.md +3 -3
- package/skills/sprint-flow/AGENTS.md +3 -3
- package/skills/test-specification-alignment/AGENTS.md +3 -3
package/adapter-common.sh
CHANGED
|
@@ -104,41 +104,30 @@ detect_project_lang() {
|
|
|
104
104
|
fi
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
"static_analysis") run_static_analysis ;;
|
|
132
|
-
"lint") run_lint ;;
|
|
133
|
-
"tests") run_tests ;;
|
|
134
|
-
"coverage") run_coverage ;;
|
|
135
|
-
*) return 1 ;;
|
|
136
|
-
esac
|
|
137
|
-
else
|
|
138
|
-
echo "No adapter found for language: $lang (searched: ~/.config/xp-gate/adapters/, githooks/adapters/)"
|
|
139
|
-
return 1
|
|
140
|
-
fi
|
|
141
|
-
}
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
# REMOVED in #688: route_to_adapter()
|
|
109
|
+
#
|
|
110
|
+
# It was a dispatcher with zero production callers. pre-commit sources an
|
|
111
|
+
# adapter and calls `run_static_analysis` / `run_lint` / `run_tests` /
|
|
112
|
+
# `run_coverage` directly; nothing ever went through this function, so its
|
|
113
|
+
# `case "$action"` enumeration was dead too -- including the "add an
|
|
114
|
+
# `architecture` arm and it'll work" assumption in #688, which would have been
|
|
115
|
+
# a no-op.
|
|
116
|
+
#
|
|
117
|
+
# Its two never-invoked dependants are gone with it: `run_architecture()` in
|
|
118
|
+
# adapters/python.sh and adapters/go.sh. Gate 6 already implements both checks
|
|
119
|
+
# INLINE in pre-commit, and does it better than the adapter versions did:
|
|
120
|
+
# * Python: detects 4 config carriers (.import-linter.yml /
|
|
121
|
+
# import_linter_config.yml / .importlinter / pyproject [tool.importlinter]),
|
|
122
|
+
# where the adapter checked 2, and BLOCKs when lint-imports is missing
|
|
123
|
+
# instead of `require_tool ... || return 0` silently passing.
|
|
124
|
+
# * Go: reads the checker's own exit status via PIPESTATUS[0] (#630), where a
|
|
125
|
+
# `arch-go check | head` pipeline would have reported head's status.
|
|
126
|
+
# Wiring the adapter version back in would therefore have been a regression,
|
|
127
|
+
# so the dead code was deleted instead of connected.
|
|
128
|
+
#
|
|
129
|
+
# tests: githooks/__tests__/issue-688-dead-dispatcher-removed.test.bats
|
|
130
|
+
# ---------------------------------------------------------------------------
|
|
142
131
|
|
|
143
132
|
check_if_tool_available() {
|
|
144
133
|
local tool_name="$1"
|
package/adapters/go.sh
CHANGED
|
@@ -43,17 +43,12 @@ run_lint() {
|
|
|
43
43
|
return $?
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
run_architecture()
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
echo "Running Go architecture checks (arch-go)..."
|
|
54
|
-
arch-go check
|
|
55
|
-
return $?
|
|
56
|
-
}
|
|
46
|
+
# REMOVED in #688: run_architecture()
|
|
47
|
+
# Never invoked -- its only would-be caller, route_to_adapter(), had no callers
|
|
48
|
+
# either. Gate 6 implements arch-go in pre-commit (grep "arch-go"), and reads
|
|
49
|
+
# the checker's own exit status via PIPESTATUS[0] (#630) instead of letting a
|
|
50
|
+
# `| head` pipeline report head's. See githooks/adapter-common.sh for the full
|
|
51
|
+
# rationale.
|
|
57
52
|
|
|
58
53
|
run_tests() {
|
|
59
54
|
require_tool go "Go SDK" || return 1
|
package/adapters/kotlin.sh
CHANGED
|
@@ -14,30 +14,75 @@ run_static_analysis() {
|
|
|
14
14
|
local build_system
|
|
15
15
|
build_system=$(_detect_kotlin_build)
|
|
16
16
|
|
|
17
|
+
# (#706) A failed BUILD must not swallow a lint tool that works on its own.
|
|
18
|
+
#
|
|
19
|
+
# This used to `return 1` the moment Gradle/Maven failed to compile, so detekt
|
|
20
|
+
# -- which reads .kt sources directly and needs no build at all -- never ran.
|
|
21
|
+
# Measured on a machine whose Gradle cannot resolve the Kotlin plugin
|
|
22
|
+
# (`Failed to query the value of property 'buildFlowServiceProperty'`): the
|
|
23
|
+
# hook reported "SKIPPED - Code quality (tools unavailable or failed)" while
|
|
24
|
+
# `detekt --input .` on the same file reported two real findings. The gate
|
|
25
|
+
# blamed the toolchain for a verdict the toolchain never got to deliver.
|
|
26
|
+
#
|
|
27
|
+
# So: record the compilation failure, still run the standalone lint tool, and
|
|
28
|
+
# let the caller decide. The failure is reported, never hidden.
|
|
29
|
+
local build_failed=0
|
|
17
30
|
if [ "$build_system" = "maven" ]; then
|
|
18
31
|
mvn compile -q 2>&1 | grep -i "error\|fail" | sed -n '1,5p; 5q'
|
|
19
32
|
if [ ${PIPESTATUS[0]} -ne 0 ]; then
|
|
20
33
|
echo "❌ Maven compilation failed"
|
|
21
|
-
|
|
34
|
+
build_failed=1
|
|
22
35
|
fi
|
|
23
36
|
elif [ "$build_system" = "gradle" ]; then
|
|
24
37
|
gradle compileKotlin --quiet 2>&1 | grep -i "error\|fail" | sed -n '1,5p; 5q'
|
|
25
38
|
if [ ${PIPESTATUS[0]} -ne 0 ]; then
|
|
26
39
|
echo "❌ Gradle compilation failed"
|
|
27
|
-
|
|
40
|
+
build_failed=1
|
|
28
41
|
fi
|
|
29
42
|
fi
|
|
30
43
|
|
|
44
|
+
# Standalone analyzers run regardless of the build outcome: their verdict does
|
|
45
|
+
# not depend on the build having succeeded.
|
|
46
|
+
#
|
|
47
|
+
# ktlint and detekt are tried in preference order, but a tool that is PRESENT
|
|
48
|
+
# and produces NOTHING is not the same as a tool that reached a verdict. An
|
|
49
|
+
# `elif` chain made the first present tool authoritative: a broken ktlint (or a
|
|
50
|
+
# shim on PATH, or ktlint 1.x refusing a `**/*.kt` glob) reported "No files
|
|
51
|
+
# matched" and detekt -- which works -- was never consulted. Measured here:
|
|
52
|
+
# `command -v ktlint` succeeded while `ktlint **/*.kt` matched zero files, so
|
|
53
|
+
# the real analyzer never ran.
|
|
54
|
+
local lint_rc=0 linted=0 ktlint_out=""
|
|
31
55
|
if command -v ktlint &>/dev/null; then
|
|
32
|
-
ktlint "**/*.kt" 2>&1
|
|
33
|
-
|
|
34
|
-
|
|
56
|
+
ktlint_out=$(ktlint "**/*.kt" 2>&1)
|
|
57
|
+
lint_rc=$?
|
|
58
|
+
printf '%s\n' "$ktlint_out" | sed -n '1,20p'
|
|
59
|
+
# Only an EXECUTED ktlint counts as having delivered a verdict. Three ways it
|
|
60
|
+
# can fail to: the binary cannot run (127/126), it reports "No files matched"
|
|
61
|
+
# (ktlint 1.8.0 no longer resolves `**/*.kt`), or it printed nothing at all.
|
|
62
|
+
# In every one of those cases detekt should speak instead of ktlint's silence
|
|
63
|
+
# being read as "clean".
|
|
64
|
+
if [ "$lint_rc" -ne 127 ] && [ "$lint_rc" -ne 126 ] \
|
|
65
|
+
&& [ -n "$ktlint_out" ] \
|
|
66
|
+
&& ! printf '%s\n' "$ktlint_out" | grep -q 'No files matched'; then
|
|
67
|
+
linted=1
|
|
68
|
+
fi
|
|
69
|
+
fi
|
|
70
|
+
if [ "$linted" -eq 0 ] && command -v detekt &>/dev/null; then
|
|
35
71
|
detekt --input . 2>&1 | sed -n '1,20p; 20q'
|
|
36
|
-
|
|
37
|
-
|
|
72
|
+
lint_rc=${PIPESTATUS[0]}
|
|
73
|
+
linted=1
|
|
74
|
+
fi
|
|
75
|
+
if [ "$linted" -eq 0 ]; then
|
|
38
76
|
echo "No Kotlin lint tools (ktlint/detekt)"
|
|
39
|
-
|
|
77
|
+
# A missing lint tool is a SKIP, not a pass -- but it must not manufacture a
|
|
78
|
+
# failure either. Only the build result decides in that case.
|
|
79
|
+
[ "$build_failed" -eq 0 ] && return 0
|
|
80
|
+
return 1
|
|
40
81
|
fi
|
|
82
|
+
|
|
83
|
+
# A lint finding is a finding whether or not the build also failed.
|
|
84
|
+
[ "$lint_rc" -ne 0 ] && return "$lint_rc"
|
|
85
|
+
return "$build_failed"
|
|
41
86
|
}
|
|
42
87
|
|
|
43
88
|
run_lint() {
|
package/adapters/python.sh
CHANGED
|
@@ -180,17 +180,12 @@ run_lint() {
|
|
|
180
180
|
fi
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
run_architecture()
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
echo "Running Python architecture checks (import-linter)..."
|
|
191
|
-
lint-imports
|
|
192
|
-
return $?
|
|
193
|
-
}
|
|
183
|
+
# REMOVED in #688: run_architecture()
|
|
184
|
+
# Never invoked -- its only would-be caller, route_to_adapter(), had no callers
|
|
185
|
+
# either. Gate 6 implements import-linter in pre-commit (grep "import-linter"),
|
|
186
|
+
# and more thoroughly: it detects 4 config carriers instead of the 2 checked
|
|
187
|
+
# here, and BLOCKs on a missing lint-imports rather than `|| return 0` passing.
|
|
188
|
+
# See githooks/adapter-common.sh for the full rationale.
|
|
194
189
|
|
|
195
190
|
run_tests() {
|
|
196
191
|
require_tool pytest "pytest" || return 1
|
|
@@ -525,7 +525,21 @@ gate13_javac_args() {
|
|
|
525
525
|
gate13_count_lint_findings() {
|
|
526
526
|
local src="${1:-}"
|
|
527
527
|
[ -n "$src" ] || src=$(cat)
|
|
528
|
-
|
|
528
|
+
# (#701) Pin the locale to C for this awk, on purpose. In a UTF-8 locale gawk
|
|
529
|
+
# refuses to let `.` span bytes that are not valid UTF-8, and a Maven build on a
|
|
530
|
+
# zh_CN Windows box writes its reactor summary in GBK (stdout.encoding=GBK), so a
|
|
531
|
+
# module name such as "验证程序" arrives as d1 e9 d6 a4 b3 cc d0 f2 -- invalid
|
|
532
|
+
# UTF-8. The #673 exclusion rule (`/^\[INFO\].*\[jar\].../`) then fails to match
|
|
533
|
+
# and the line falls through to the open-category rule as a phantom finding.
|
|
534
|
+
# Measured (#701): the same input counts 0 under LC_ALL=C and 1 under
|
|
535
|
+
# LC_ALL=C.UTF-8, i.e. the verdict depended on the caller's environment rather
|
|
536
|
+
# than on the build. C locale makes `.` byte-oriented, so the match no longer
|
|
537
|
+
# depends on whether the module name happens to be ASCII.
|
|
538
|
+
#
|
|
539
|
+
# This costs nothing in matching power: every marker below (packaging types,
|
|
540
|
+
# `[category]`, `warning:`) is ASCII, and the localised unchecked/unsafe
|
|
541
|
+
# wordings are matched as byte sequences, which C locale handles exactly.
|
|
542
|
+
printf '%s\n' "$src" | LC_ALL=C awk '
|
|
529
543
|
BEGIN { n = 0 }
|
|
530
544
|
# (#673) Maven 3.9.7+ reactor summary appends the packaging type per module:
|
|
531
545
|
# [INFO] whaleaudit-core [jar]
|
package/hooks/adapter-common.sh
CHANGED
|
@@ -104,41 +104,30 @@ detect_project_lang() {
|
|
|
104
104
|
fi
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
"static_analysis") run_static_analysis ;;
|
|
132
|
-
"lint") run_lint ;;
|
|
133
|
-
"tests") run_tests ;;
|
|
134
|
-
"coverage") run_coverage ;;
|
|
135
|
-
*) return 1 ;;
|
|
136
|
-
esac
|
|
137
|
-
else
|
|
138
|
-
echo "No adapter found for language: $lang (searched: ~/.config/xp-gate/adapters/, githooks/adapters/)"
|
|
139
|
-
return 1
|
|
140
|
-
fi
|
|
141
|
-
}
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
# REMOVED in #688: route_to_adapter()
|
|
109
|
+
#
|
|
110
|
+
# It was a dispatcher with zero production callers. pre-commit sources an
|
|
111
|
+
# adapter and calls `run_static_analysis` / `run_lint` / `run_tests` /
|
|
112
|
+
# `run_coverage` directly; nothing ever went through this function, so its
|
|
113
|
+
# `case "$action"` enumeration was dead too -- including the "add an
|
|
114
|
+
# `architecture` arm and it'll work" assumption in #688, which would have been
|
|
115
|
+
# a no-op.
|
|
116
|
+
#
|
|
117
|
+
# Its two never-invoked dependants are gone with it: `run_architecture()` in
|
|
118
|
+
# adapters/python.sh and adapters/go.sh. Gate 6 already implements both checks
|
|
119
|
+
# INLINE in pre-commit, and does it better than the adapter versions did:
|
|
120
|
+
# * Python: detects 4 config carriers (.import-linter.yml /
|
|
121
|
+
# import_linter_config.yml / .importlinter / pyproject [tool.importlinter]),
|
|
122
|
+
# where the adapter checked 2, and BLOCKs when lint-imports is missing
|
|
123
|
+
# instead of `require_tool ... || return 0` silently passing.
|
|
124
|
+
# * Go: reads the checker's own exit status via PIPESTATUS[0] (#630), where a
|
|
125
|
+
# `arch-go check | head` pipeline would have reported head's status.
|
|
126
|
+
# Wiring the adapter version back in would therefore have been a regression,
|
|
127
|
+
# so the dead code was deleted instead of connected.
|
|
128
|
+
#
|
|
129
|
+
# tests: githooks/__tests__/issue-688-dead-dispatcher-removed.test.bats
|
|
130
|
+
# ---------------------------------------------------------------------------
|
|
142
131
|
|
|
143
132
|
check_if_tool_available() {
|
|
144
133
|
local tool_name="$1"
|
|
@@ -525,7 +525,21 @@ gate13_javac_args() {
|
|
|
525
525
|
gate13_count_lint_findings() {
|
|
526
526
|
local src="${1:-}"
|
|
527
527
|
[ -n "$src" ] || src=$(cat)
|
|
528
|
-
|
|
528
|
+
# (#701) Pin the locale to C for this awk, on purpose. In a UTF-8 locale gawk
|
|
529
|
+
# refuses to let `.` span bytes that are not valid UTF-8, and a Maven build on a
|
|
530
|
+
# zh_CN Windows box writes its reactor summary in GBK (stdout.encoding=GBK), so a
|
|
531
|
+
# module name such as "验证程序" arrives as d1 e9 d6 a4 b3 cc d0 f2 -- invalid
|
|
532
|
+
# UTF-8. The #673 exclusion rule (`/^\[INFO\].*\[jar\].../`) then fails to match
|
|
533
|
+
# and the line falls through to the open-category rule as a phantom finding.
|
|
534
|
+
# Measured (#701): the same input counts 0 under LC_ALL=C and 1 under
|
|
535
|
+
# LC_ALL=C.UTF-8, i.e. the verdict depended on the caller's environment rather
|
|
536
|
+
# than on the build. C locale makes `.` byte-oriented, so the match no longer
|
|
537
|
+
# depends on whether the module name happens to be ASCII.
|
|
538
|
+
#
|
|
539
|
+
# This costs nothing in matching power: every marker below (packaging types,
|
|
540
|
+
# `[category]`, `warning:`) is ASCII, and the localised unchecked/unsafe
|
|
541
|
+
# wordings are matched as byte sequences, which C locale handles exactly.
|
|
542
|
+
printf '%s\n' "$src" | LC_ALL=C awk '
|
|
529
543
|
BEGIN { n = 0 }
|
|
530
544
|
# (#673) Maven 3.9.7+ reactor summary appends the packaging type per module:
|
|
531
545
|
# [INFO] whaleaudit-core [jar]
|
package/hooks/pre-commit
CHANGED
|
@@ -2237,15 +2237,22 @@ else
|
|
|
2237
2237
|
ADAPTER_FILE=$(resolve_adapter_path "$CURRENT_LANG")
|
|
2238
2238
|
if [ -n "$ADAPTER_FILE" ] && source "$ADAPTER_FILE" 2>/dev/null; then
|
|
2239
2239
|
echo "Running static analysis for $CURRENT_LANG..."
|
|
2240
|
+
# (#706) Capture the ADAPTER's verdict, not `head`'s.
|
|
2241
|
+
#
|
|
2242
|
+
# `run_static_analysis | head -30` makes `$?` the exit status of `head`,
|
|
2243
|
+
# which is 0 whenever it manages to read anything -- so ANALYSIS_EXIT was
|
|
2244
|
+
# always 0 and the "tools unavailable or failed" branch below was dead
|
|
2245
|
+
# code. Measured: an adapter returning 1 through this pipe still produced
|
|
2246
|
+
# PASSED. PIPESTATUS[0] carries the adapter's real status.
|
|
2240
2247
|
run_static_analysis | head -30 2>/dev/null
|
|
2241
|
-
ANALYSIS_EXIT
|
|
2248
|
+
ANALYSIS_EXIT=${PIPESTATUS[0]}
|
|
2242
2249
|
if [ "$ANALYSIS_EXIT" -ne 0 ]; then
|
|
2243
2250
|
echo "⚠️ Static analysis had issues (may be non-blocking)"
|
|
2244
2251
|
fi
|
|
2245
|
-
|
|
2252
|
+
|
|
2246
2253
|
echo "Running lint check for $CURRENT_LANG..."
|
|
2247
2254
|
run_lint | head -30 2>/dev/null
|
|
2248
|
-
LINT_EXIT
|
|
2255
|
+
LINT_EXIT=${PIPESTATUS[0]}
|
|
2249
2256
|
if [ "$LINT_EXIT" -ne 0 ]; then
|
|
2250
2257
|
echo "ℹ️ Lint check had issues (may be non-blocking)"
|
|
2251
2258
|
fi
|
|
@@ -3692,10 +3699,40 @@ else
|
|
|
3692
3699
|
if [ "${PARTIAL_TEST_RUN:-false}" = "true" ]; then
|
|
3693
3700
|
echo "⚠️ No JaCoCo report found (subset test run) — not blocking."
|
|
3694
3701
|
else
|
|
3702
|
+
# (#703) Diagnose BEFORE advising. The old message sent every user to
|
|
3703
|
+
# `mvn verify`; on a build path containing non-ASCII characters that
|
|
3704
|
+
# command is a dead end. Measured on a zh_CN Windows box at
|
|
3705
|
+
# D:\projects\SQL审计工具: the JaCoCo agent never writes jacoco.exec, it
|
|
3706
|
+
# does not throw, and Maven reports BUILD SUCCESS with
|
|
3707
|
+
# [INFO] Skipping JaCoCo execution due to missing execution data file.
|
|
3708
|
+
# so `mvn verify` "fixes" nothing and the user loops. Splitting the
|
|
3709
|
+
# diagnosis out lets each case get an action that can actually work.
|
|
3695
3710
|
echo "❌ BLOCKED - No JaCoCo report found, so the ${COVERAGE_THRESHOLD:-80}% Java coverage"
|
|
3696
|
-
echo " floor cannot be verified.
|
|
3697
|
-
|
|
3698
|
-
|
|
3711
|
+
echo " floor cannot be verified."
|
|
3712
|
+
# The distinguishing symptom is the jacoco.exec execution data file, not
|
|
3713
|
+
# a missing report: a report that failed to be GENERATED and an agent
|
|
3714
|
+
# that never WROTE are different problems with different fixes.
|
|
3715
|
+
echo " Likely cause: the JaCoCo agent did not write its execution data file"
|
|
3716
|
+
echo " (target/jacoco.exec), which is what 'Skipping JaCoCo execution due to"
|
|
3717
|
+
echo " missing execution data file' in the Maven log means."
|
|
3718
|
+
# A non-ASCII working directory is the one common cause where the usual
|
|
3719
|
+
# advice actively does not work, so it must be called out by name rather
|
|
3720
|
+
# than left for the user to discover by trying.
|
|
3721
|
+
case "$PWD" in
|
|
3722
|
+
*[!-~]*)
|
|
3723
|
+
echo " ⚠️ This build path contains non-ASCII characters: ${PWD}"
|
|
3724
|
+
echo " ⚠️ Re-running 'mvn verify' will NOT help on this path — JaCoCo"
|
|
3725
|
+
echo " silently skips writing execution data there. Build through an"
|
|
3726
|
+
echo " ASCII-only directory instead (e.g. a junction to the same tree,"
|
|
3727
|
+
echo " entered with cmd's 'pushd'/'cd /d' — Git Bash's cd resolves the"
|
|
3728
|
+
echo " junction back to the non-ASCII path, so it does not help)."
|
|
3729
|
+
;;
|
|
3730
|
+
*)
|
|
3731
|
+
echo " To produce it, run 'mvn verify' or 'gradle jacocoTestReport' so that"
|
|
3732
|
+
echo " target/site/jacoco/jacoco.csv exists (see also: JaCoCo detection"
|
|
3733
|
+
echo " in adapters/java.sh)."
|
|
3734
|
+
;;
|
|
3735
|
+
esac
|
|
3699
3736
|
exit 1
|
|
3700
3737
|
fi
|
|
3701
3738
|
fi
|
|
@@ -4539,6 +4576,91 @@ resolve_build_integrity_timeout() {
|
|
|
4539
4576
|
esac
|
|
4540
4577
|
}
|
|
4541
4578
|
|
|
4579
|
+
# (#708) A Java build failure has one overwhelmingly common cause that the gate
|
|
4580
|
+
# used to say nothing about: the wrong JDK. The real reason is buried in the last
|
|
4581
|
+
# 20 lines of Maven output, while the gate's own verdict line was only
|
|
4582
|
+
# "❌ Java build failed (exit 1)".
|
|
4583
|
+
#
|
|
4584
|
+
# Measured: a pom with <maven.compiler.release>8</maven.compiler.release> built
|
|
4585
|
+
# with JDK 8 fails with "无效的标记: --release" (invalid flag: --release) — a
|
|
4586
|
+
# message that points at the compiler ARGUMENTS and at the project CONFIG while
|
|
4587
|
+
# actually pointing at the JDK on PATH. Getting from that message to "change the
|
|
4588
|
+
# JDK" takes one inference, and the gate's verdict line supplied no premise for
|
|
4589
|
+
# it. The person debugging this suspected the release setting, then the plugin
|
|
4590
|
+
# version, then .mvn/jvm.config — all wrong; the fix was a different JDK.
|
|
4591
|
+
#
|
|
4592
|
+
# So: on failure, print what JDK actually ran, what the project asks for, and —
|
|
4593
|
+
# when the output carries the tell — state the conclusion instead of the symptom.
|
|
4594
|
+
# Diagnostics must never change the verdict; this only adds lines.
|
|
4595
|
+
java_bi_report_jdk_context() {
|
|
4596
|
+
local build_out="$1"
|
|
4597
|
+
local java_version="" java_home_val="${JAVA_HOME:-}"
|
|
4598
|
+
# `java -version` writes to stderr on every JDK. Take the version line verbatim
|
|
4599
|
+
# rather than parsing it: this is a diagnostic, and the raw line is what a
|
|
4600
|
+
# person would paste into a bug report.
|
|
4601
|
+
if command -v java >/dev/null 2>&1; then
|
|
4602
|
+
java_version=$(java -version 2>&1 | sed -n '1p')
|
|
4603
|
+
fi
|
|
4604
|
+
if [ -n "$java_version" ]; then
|
|
4605
|
+
echo " JDK in use: ${java_version}"
|
|
4606
|
+
else
|
|
4607
|
+
echo " JDK in use: (java not on PATH)"
|
|
4608
|
+
fi
|
|
4609
|
+
if [ -n "$java_home_val" ]; then
|
|
4610
|
+
echo " JAVA_HOME: ${java_home_val}"
|
|
4611
|
+
else
|
|
4612
|
+
echo " JAVA_HOME: (unset)"
|
|
4613
|
+
fi
|
|
4614
|
+
|
|
4615
|
+
# What the project requires. Maven declares it three ways; Gradle declares it in
|
|
4616
|
+
# its build script. Report whichever exists — the comparison is the user's, but
|
|
4617
|
+
# only if both halves are visible.
|
|
4618
|
+
local declared=""
|
|
4619
|
+
if [ -f "pom.xml" ]; then
|
|
4620
|
+
local mvn_release mvn_source
|
|
4621
|
+
mvn_release=$(grep -o '<maven\.compiler\.release>[[:space:]]*[0-9][0-9]*' pom.xml 2>/dev/null \
|
|
4622
|
+
| sed 's/.*>[[:space:]]*//' | head -1)
|
|
4623
|
+
mvn_source=$(grep -o '<maven\.compiler\.source>[[:space:]]*[0-9][0-9]*' pom.xml 2>/dev/null \
|
|
4624
|
+
| sed 's/.*>[[:space:]]*//' | head -1)
|
|
4625
|
+
if [ -n "$mvn_release" ]; then
|
|
4626
|
+
declared="maven.compiler.release=${mvn_release}"
|
|
4627
|
+
elif [ -n "$mvn_source" ]; then
|
|
4628
|
+
declared="maven.compiler.source=${mvn_source}"
|
|
4629
|
+
fi
|
|
4630
|
+
fi
|
|
4631
|
+
if [ -z "$declared" ] && [ -f "build.gradle" ]; then
|
|
4632
|
+
local g_compat
|
|
4633
|
+
g_compat=$(grep -o "sourceCompatibility[[:space:]]*=[[:space:]]*['\"]\{0,1\}[0-9][0-9]*" build.gradle 2>/dev/null \
|
|
4634
|
+
| sed "s/.*[=][[:space:]]*['\"]\{0,1\}//" | head -1)
|
|
4635
|
+
[ -n "$g_compat" ] && declared="sourceCompatibility=${g_compat}"
|
|
4636
|
+
fi
|
|
4637
|
+
if [ -z "$declared" ] && [ -f "build.gradle.kts" ]; then
|
|
4638
|
+
local k_compat
|
|
4639
|
+
k_compat=$(grep -o 'sourceCompatibility[[:space:]]*=[[:space:]]*JavaVersion\.VERSION_[0-9_]*' build.gradle.kts 2>/dev/null \
|
|
4640
|
+
| sed 's/.*VERSION_//' | tr '_' '.' | head -1)
|
|
4641
|
+
[ -n "$k_compat" ] && declared="sourceCompatibility=${k_compat}"
|
|
4642
|
+
fi
|
|
4643
|
+
|
|
4644
|
+
if [ -n "$declared" ]; then
|
|
4645
|
+
echo " Project requires: ${declared}"
|
|
4646
|
+
else
|
|
4647
|
+
echo " Project requires: (no JDK floor declared in pom.xml/build.gradle)"
|
|
4648
|
+
fi
|
|
4649
|
+
|
|
4650
|
+
# The tell. `--release` only exists from JDK 9, so its rejection is a direct
|
|
4651
|
+
# statement about the JDK, not about the pom. Saying so converts a search into
|
|
4652
|
+
# a fix.
|
|
4653
|
+
case "$build_out" in
|
|
4654
|
+
*--release*)
|
|
4655
|
+
if printf '%s' "$build_out" | grep -qiE 'invalid flag|无效的标记|illegal option|unrecognized|unknown option'; then
|
|
4656
|
+
echo " 💡 The build passed '--release' to javac and the JDK rejected it."
|
|
4657
|
+
echo " '--release' requires JDK 9 or newer; the JDK above is older than that."
|
|
4658
|
+
echo " Switch to a JDK >= 9 (or remove the release/source setting if it is not intended)."
|
|
4659
|
+
fi
|
|
4660
|
+
;;
|
|
4661
|
+
esac
|
|
4662
|
+
}
|
|
4663
|
+
|
|
4542
4664
|
run_java_build_integrity() {
|
|
4543
4665
|
local timeout_s build_bin
|
|
4544
4666
|
# Wrapper first: mvnw pins the project's own Maven version.
|
|
@@ -4553,12 +4675,17 @@ run_java_build_integrity() {
|
|
|
4553
4675
|
fi
|
|
4554
4676
|
|
|
4555
4677
|
timeout_s=$(resolve_build_integrity_timeout)
|
|
4678
|
+
# (#708) Keep the output: the failure branch has to read it to tell a wrong-JDK
|
|
4679
|
+
# failure from any other one. Displayed through `tail -20` exactly as before, so
|
|
4680
|
+
# the visible build log is unchanged.
|
|
4681
|
+
local bi_out
|
|
4556
4682
|
if command -v timeout >/dev/null 2>&1; then
|
|
4557
|
-
timeout "$timeout_s" "$build_bin" -q -DskipTests compile 2>&1
|
|
4683
|
+
bi_out=$(timeout "$timeout_s" "$build_bin" -q -DskipTests compile 2>&1)
|
|
4558
4684
|
else
|
|
4559
|
-
"$build_bin" -q -DskipTests compile 2>&1
|
|
4685
|
+
bi_out=$("$build_bin" -q -DskipTests compile 2>&1)
|
|
4560
4686
|
fi
|
|
4561
|
-
local rc
|
|
4687
|
+
local rc=$?
|
|
4688
|
+
printf '%s\n' "$bi_out" | tail -20
|
|
4562
4689
|
if [ "$rc" -eq 0 ]; then
|
|
4563
4690
|
echo " ✅ Java build integrity verified (mvn -q -DskipTests compile)."
|
|
4564
4691
|
return 0
|
|
@@ -4569,6 +4696,7 @@ run_java_build_integrity() {
|
|
|
4569
4696
|
return 3
|
|
4570
4697
|
fi
|
|
4571
4698
|
echo " ❌ Java build failed (exit $rc)"
|
|
4699
|
+
java_bi_report_jdk_context "$bi_out"
|
|
4572
4700
|
return 2
|
|
4573
4701
|
}
|
|
4574
4702
|
|
|
@@ -403,7 +403,7 @@ describe('doctor delphi #684 global config fallback', () => {
|
|
|
403
403
|
return { logs, restore: () => spy.mockRestore() };
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
-
it('AC-
|
|
406
|
+
it('AC-721-01: a valid global config with no project copy PASSes instead of failing', async () => {
|
|
407
407
|
const globalPath = writeGlobalConfig();
|
|
408
408
|
const project = path.join(tmpHome, 'a-project-with-no-config');
|
|
409
409
|
fs.mkdirSync(project, { recursive: true });
|
|
@@ -417,7 +417,7 @@ describe('doctor delphi #684 global config fallback', () => {
|
|
|
417
417
|
expect(cfgLine).toMatch(/global/i);
|
|
418
418
|
});
|
|
419
419
|
|
|
420
|
-
it('AC-
|
|
420
|
+
it('AC-721-02: no config in either scope still FAILs (fallback must not mask a broken install)', async () => {
|
|
421
421
|
const project = path.join(tmpHome, 'bare-project');
|
|
422
422
|
fs.mkdirSync(project, { recursive: true });
|
|
423
423
|
const { logs, restore } = captureLogs();
|
|
@@ -436,7 +436,7 @@ describe('doctor delphi #684 global config fallback', () => {
|
|
|
436
436
|
expect(cfgLine).toMatch(/not found/);
|
|
437
437
|
});
|
|
438
438
|
|
|
439
|
-
it('AC-
|
|
439
|
+
it('AC-721-03: a project config still wins over the global one', async () => {
|
|
440
440
|
const globalPath = writeGlobalConfig();
|
|
441
441
|
const project = path.join(tmpHome, 'project-with-own-config');
|
|
442
442
|
fs.mkdirSync(project, { recursive: true });
|
|
@@ -336,7 +336,7 @@ describe('doctor-tui skill freshness #681 equal version, different content', ()
|
|
|
336
336
|
return { issues, check: checks.find((c) => c.name === `Skill: ${name}`) };
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
-
it('AC-
|
|
339
|
+
it('AC-718-01: same version but a missing chapter is WARN, not "Up to date"', () => {
|
|
340
340
|
const bundled = fs.readFileSync(path.join(bundledSkillsDir, 'delphi-review', 'SKILL.md'), 'utf8');
|
|
341
341
|
// Keep the frontmatter (version included), drop a large slice of the body.
|
|
342
342
|
const idx = bundled.indexOf('\n---', 4);
|
|
@@ -352,7 +352,7 @@ describe('doctor-tui skill freshness #681 equal version, different content', ()
|
|
|
352
352
|
expect(issues).toBe(1);
|
|
353
353
|
});
|
|
354
354
|
|
|
355
|
-
it('AC-
|
|
355
|
+
it('AC-718-02: same version and same content is still "Up to date" (no false alarm)', () => {
|
|
356
356
|
const bundled = fs.readFileSync(path.join(bundledSkillsDir, 'delphi-review', 'SKILL.md'), 'utf8');
|
|
357
357
|
installSkill('delphi-review', bundled);
|
|
358
358
|
const { issues, check } = runFreshness('delphi-review');
|
|
@@ -360,7 +360,7 @@ describe('doctor-tui skill freshness #681 equal version, different content', ()
|
|
|
360
360
|
expect(issues).toBe(0);
|
|
361
361
|
});
|
|
362
362
|
|
|
363
|
-
it('AC-
|
|
363
|
+
it('AC-718-03: line endings alone still do not count as a difference', () => {
|
|
364
364
|
const bundled = fs.readFileSync(path.join(bundledSkillsDir, 'delphi-review', 'SKILL.md'), 'utf8');
|
|
365
365
|
installSkill('delphi-review', bundled.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n'));
|
|
366
366
|
const { issues, check } = runFreshness('delphi-review');
|