@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.
Files changed (81) hide show
  1. package/adapters/java.sh +373 -27
  2. package/gate-3.sh +51 -3
  3. package/gate-4.sh +32 -1
  4. package/hooks/gate-3.sh +51 -3
  5. package/hooks/gate-4.sh +32 -1
  6. package/hooks/pre-commit +275 -36
  7. package/lib/__tests__/doctor-spawn-capability.test.js +116 -0
  8. package/lib/__tests__/doctor-tui-skill-freshness.test.js +49 -10
  9. package/lib/doctor-tui.js +21 -3
  10. package/lib/doctor.js +111 -8
  11. package/lib/gate-audit.ts +3 -0
  12. package/lib/mattpocock-license-verdict.json +1 -1
  13. package/mock-policy/AGENTS.md +2 -2
  14. package/mutation/AGENTS.md +2 -2
  15. package/package.json +1 -1
  16. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  17. package/plugins/claude-code/skills/admin-template-guidelines/SKILL.md +1 -0
  18. package/plugins/claude-code/skills/batch-grill-me/SKILL.md +1 -0
  19. package/plugins/claude-code/skills/clipboard-vision/SKILL.md +1 -0
  20. package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
  21. package/plugins/claude-code/skills/domain-modeling/SKILL.md +1 -0
  22. package/plugins/claude-code/skills/grill-with-docs/SKILL.md +1 -0
  23. package/plugins/claude-code/skills/grilling/SKILL.md +1 -0
  24. package/plugins/claude-code/skills/improve-codebase-architecture/SKILL.md +1 -0
  25. package/plugins/claude-code/skills/ralph-loop/SKILL.md +1 -0
  26. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
  27. package/plugins/claude-code/skills/test-driven-development/SKILL.md +1 -0
  28. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
  29. package/plugins/claude-code/skills/test-specification-alignment/SKILL.md +1 -0
  30. package/plugins/claude-code/skills/to-issues/SKILL.md +1 -0
  31. package/plugins/dsh/package.json +1 -1
  32. package/plugins/dsh/skills/admin-template-guidelines/SKILL.md +1 -0
  33. package/plugins/dsh/skills/batch-grill-me/SKILL.md +1 -0
  34. package/plugins/dsh/skills/clipboard-vision/SKILL.md +1 -0
  35. package/plugins/dsh/skills/delphi-review/AGENTS.md +2 -2
  36. package/plugins/dsh/skills/domain-modeling/SKILL.md +1 -0
  37. package/plugins/dsh/skills/grill-with-docs/SKILL.md +1 -0
  38. package/plugins/dsh/skills/grilling/SKILL.md +1 -0
  39. package/plugins/dsh/skills/improve-codebase-architecture/SKILL.md +1 -0
  40. package/plugins/dsh/skills/ralph-loop/SKILL.md +1 -0
  41. package/plugins/dsh/skills/sprint-flow/AGENTS.md +2 -2
  42. package/plugins/dsh/skills/test-driven-development/SKILL.md +1 -0
  43. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +2 -2
  44. package/plugins/dsh/skills/test-specification-alignment/SKILL.md +1 -0
  45. package/plugins/dsh/skills/to-issues/SKILL.md +1 -0
  46. package/plugins/opencode/package.json +1 -1
  47. package/plugins/opencode/skills/admin-template-guidelines/SKILL.md +1 -0
  48. package/plugins/opencode/skills/batch-grill-me/SKILL.md +1 -0
  49. package/plugins/opencode/skills/clipboard-vision/SKILL.md +1 -0
  50. package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
  51. package/plugins/opencode/skills/domain-modeling/SKILL.md +1 -0
  52. package/plugins/opencode/skills/grill-with-docs/SKILL.md +1 -0
  53. package/plugins/opencode/skills/grilling/SKILL.md +1 -0
  54. package/plugins/opencode/skills/improve-codebase-architecture/SKILL.md +1 -0
  55. package/plugins/opencode/skills/ralph-loop/SKILL.md +1 -0
  56. package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
  57. package/plugins/opencode/skills/test-driven-development/SKILL.md +1 -0
  58. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
  59. package/plugins/opencode/skills/test-specification-alignment/SKILL.md +1 -0
  60. package/plugins/opencode/skills/to-issues/SKILL.md +1 -0
  61. package/plugins/qoder/plugin.json +1 -1
  62. package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
  63. package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
  64. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
  65. package/principles/AGENTS.md +2 -2
  66. package/principles/adapters/__tests__/java.test.ts +317 -0
  67. package/principles/adapters/java.ts +325 -13
  68. package/skills/admin-template-guidelines/SKILL.md +1 -0
  69. package/skills/batch-grill-me/SKILL.md +1 -0
  70. package/skills/clipboard-vision/SKILL.md +1 -0
  71. package/skills/delphi-review/AGENTS.md +2 -2
  72. package/skills/domain-modeling/SKILL.md +1 -0
  73. package/skills/grill-with-docs/SKILL.md +1 -0
  74. package/skills/grilling/SKILL.md +1 -0
  75. package/skills/improve-codebase-architecture/SKILL.md +1 -0
  76. package/skills/ralph-loop/SKILL.md +1 -0
  77. package/skills/sprint-flow/AGENTS.md +2 -2
  78. package/skills/test-driven-development/SKILL.md +1 -0
  79. package/skills/test-specification-alignment/AGENTS.md +2 -2
  80. package/skills/test-specification-alignment/SKILL.md +1 -0
  81. package/skills/to-issues/SKILL.md +1 -0
package/adapters/java.sh CHANGED
@@ -1,10 +1,280 @@
1
1
  #!/usr/bin/env bash
2
+ # ============================================================================
3
+ # Java adapter -- GATE 1 static analysis (#507 S1, DR-001/DR-002)
4
+ #
5
+ # Primary engine: wc-java-lint (`check --staged --format json`), fail-closed:
6
+ # exit 0 -> PASS (clean)
7
+ # exit 1 -> FAIL (violations; never downgraded in soft mode)
8
+ # exit >=2 -> BLOCK (execution error / timeout / unusable JSON)
9
+ # JSON without the required whitelist fields (file/rule/severity) counts as
10
+ # unusable -> BLOCK; unknown extra fields are tolerated (additive evolution).
11
+ # Escape valves:
12
+ # XP_GATE_WC_JAVA_LINT=soft downgrades BLOCK-class outcomes (exit>=2,
13
+ # timeout, unusable JSON) to non-blocking WARN (exit 3); exit-1 stays FAIL.
14
+ # XP_GATE_WC_JAVA_LINT=report downgrades exit-1 violations to non-blocking
15
+ # WARN (rollout/grace mode for legacy repos; #507 Delphi round-1 C-MAJOR-3).
16
+ # An exit-0-with-objects response is a lying tool and is NEVER downgraded.
17
+ # A SKIP/WARN reason crosses the subshell boundary as an `XP_G1_REASON: ...`
18
+ # stdout line (export cannot: the caller captures command-substitution output).
19
+ # Fallback: tool not on PATH -> legacy checkstyle/pmd + p3c/whalecloud chain;
20
+ # checkstyle AND pmd both missing -> explicit WARN and verdict must NOT be
21
+ # PASS (exit 3, recorded in the gate-1 audit detail).
22
+ #
23
+ # Verdict contract with pre-commit's `java)` branch:
24
+ # 0 = PASS, 1 = FAIL(violations -> commit blocked), 2 = BLOCK(fail-closed),
25
+ # 3 = WARN(non-blocking). pre-commit scores a WARN IN the denominator so it
26
+ # forbids an overall PASS -- Delphi round-2 A-MAJOR-1: mapping these paths
27
+ # to SKIP instead let unverified Java (no engine installed, or findings
28
+ # suppressed in report mode) ride out of the denominator to a fake 10/10.
29
+ # ============================================================================
2
30
 
3
31
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
4
32
  # Plugin directory
5
33
  PLUGIN_DIR="$SCRIPT_DIR/plugins/p3c-java"
6
34
  WHALECLOUD_PLUGIN_DIR="$SCRIPT_DIR/plugins/whalecloud-java"
7
35
 
36
+ WC_JAVA_LINT_DEFAULT_TIMEOUT=180
37
+
38
+ # ---------------------------------------------------------------------------
39
+ # Discovery (#507 AC-507-01-05): explicit multi-form support. MSYS2/Git Bash
40
+ # `command -v` resolves .exe/.cmd only when the extension is spelled out, so
41
+ # each form is probed verbatim.
42
+ # ---------------------------------------------------------------------------
43
+ wc_java_lint_bin() {
44
+ local candidate
45
+ for candidate in wc-java-lint wc-java-lint.exe wc-java-lint.cmd; do
46
+ if command -v "$candidate" >/dev/null 2>&1; then
47
+ command -v "$candidate"
48
+ return 0
49
+ fi
50
+ done
51
+ return 1
52
+ }
53
+
54
+ # ---------------------------------------------------------------------------
55
+ # Timeout (#507 AC-507-01-09): .xp-gate/wc-java-lint-timeout (positive
56
+ # integer seconds) > default 180. Invalid values WARN and fall back -- an
57
+ # unreadable override must not silently disable the timeout.
58
+ # ---------------------------------------------------------------------------
59
+ resolve_wc_java_lint_timeout() {
60
+ local file=".xp-gate/wc-java-lint-timeout" val=""
61
+ if [ -f "$file" ]; then
62
+ val=$(tr -d '[:space:]' < "$file")
63
+ fi
64
+ if [ -z "$val" ]; then
65
+ echo "$WC_JAVA_LINT_DEFAULT_TIMEOUT"
66
+ return 0
67
+ fi
68
+ case "$val" in
69
+ ''|*[!0-9]*|0)
70
+ echo "WARN - invalid $file value '${val}' (expected a positive integer), falling back to ${WC_JAVA_LINT_DEFAULT_TIMEOUT}s" >&2
71
+ echo "$WC_JAVA_LINT_DEFAULT_TIMEOUT"
72
+ ;;
73
+ *) echo "$val" ;;
74
+ esac
75
+ }
76
+
77
+ # ---------------------------------------------------------------------------
78
+ # Staged .java file set (#507 AC-507-01-10): the ONLY files handed to the
79
+ # tool in explicit-list mode. Polyglot commits keep their TS/Python files out
80
+ # of the Java engine entirely.
81
+ # ---------------------------------------------------------------------------
82
+ java_staged_files() {
83
+ # Harness/test injection: XP_GATE_JAVA_STAGED_FILES supplies a raw file
84
+ # list (newline-separated) so tests can exercise the .java filter without
85
+ # a git repo. The filter still applies -- the tool never sees non-.java.
86
+ if [ -n "${XP_GATE_JAVA_STAGED_FILES:-}" ]; then
87
+ printf '%s\n' "$XP_GATE_JAVA_STAGED_FILES" | grep -E '\.java$' || true
88
+ return 0
89
+ fi
90
+ git diff --cached --name-only --diff-filter=d 2>/dev/null \
91
+ | grep -E '\.java$' || true
92
+ }
93
+
94
+ # ---------------------------------------------------------------------------
95
+ # JSON whitelist validation (#507 AC-507-01-07, DR-002).
96
+ # Contract shape: a JSON array of FLAT violation objects, each carrying
97
+ # file/rule/severity (line optional). Checks are grep-based by design:
98
+ # - STRING LITERALS ARE STRIPPED BEFORE COUNTING (#507 Delphi round-1
99
+ # A-MAJOR-1): violation messages routinely contain braces ("'{' is not
100
+ # preceded...") or the words file/rule/severity -- counting raw text made
101
+ # perfectly valid output false-BLOCK. After stripping, only structural
102
+ # braces and structural keys remain.
103
+ # - unknown extra fields add occurrences of their own keys, never remove
104
+ # the required ones -> tolerated (additive evolution);
105
+ # - any object missing a required key desynchronizes the per-key occurrence
106
+ # counts from the object count -> unusable -> BLOCK;
107
+ # - a truncated response (no closing bracket) is unusable -> BLOCK.
108
+ # Prints the violation (object) count on success; returns 1 if unusable.
109
+ # ---------------------------------------------------------------------------
110
+ wc_java_json_violation_count() {
111
+ local json_file="$1"
112
+ [ -s "$json_file" ] || return 1
113
+
114
+ # Array-shaped: opens with [ and closes with ]. (A bare [] is valid clean
115
+ # output; an object wrapper is outside the pinned contract.)
116
+ [ "$(head -c 1 "$json_file")" = "[" ] || return 1
117
+ local tail
118
+ tail=$(tr -d '[:space:]' < "$json_file" | tail -c 1)
119
+ [ "$tail" = "]" ] || return 1
120
+
121
+ # Strip string VALUE literals (`: "..."` -> `: ""`). Keys must survive
122
+ # (they are the tokens being counted); values must vanish (violation
123
+ # messages routinely contain braces or the words file/rule/severity --
124
+ # #507 Delphi round-1 A-MAJOR-1). JSON strings cannot contain raw newlines
125
+ # (they are \n escapes), so line-wise sed is safe. The leftmost-match rule
126
+ # consumes an entire value string in one go, including any `: "` sequences
127
+ # inside it. Broken JSON masks badly -> counts desynchronize -> BLOCK,
128
+ # which is the correct fail-closed direction.
129
+ # POSIX ERE via -E: BSD sed (macOS is a supported CI platform) does not
130
+ # support the GNU BRE `\|` alternation extension -- under BSD sed the GNU
131
+ # variant either fails to strip values (count desync -> BLOCK, annoying but
132
+ # safe) or, worst case, errors to empty counts and disarms the lying-tool
133
+ # BLOCK below (fail-open). Delphi round-2 A-MAJOR-2.
134
+ local structural obj_count file_count rule_count sev_count
135
+ structural=$(sed -E 's/:([[:space:]]*)"([^"\\]|\\.)*"/:\1""/g' "$json_file" 2>/dev/null)
136
+ obj_count=$(grep -o '{' <<< "$structural" | wc -l | tr -d '[:space:]')
137
+ file_count=$(grep -o '"file"' <<< "$structural" | wc -l | tr -d '[:space:]')
138
+ rule_count=$(grep -o '"rule"' <<< "$structural" | wc -l | tr -d '[:space:]')
139
+ sev_count=$(grep -o '"severity"' <<< "$structural" | wc -l | tr -d '[:space:]')
140
+ [ "$obj_count" -eq "$file_count" ] || return 1
141
+ [ "$obj_count" -eq "$rule_count" ] || return 1
142
+ [ "$obj_count" -eq "$sev_count" ] || return 1
143
+
144
+ echo "$obj_count"
145
+ }
146
+
147
+ # Distinct staged-file names from a violations JSON (reporting only).
148
+ wc_java_json_files() {
149
+ grep -o '"file"[[:space:]]*:[[:space:]]*"[^"]*"' "$1" 2>/dev/null \
150
+ | sed 's/.*"\([^"]*\)"$/\1/' | sort -u | head -10
151
+ }
152
+
153
+ # ---------------------------------------------------------------------------
154
+ # Primary engine (#507 AC-507-01-01/02/06/09).
155
+ # Prints a human-readable report on stdout; returns the verdict code.
156
+ # ---------------------------------------------------------------------------
157
+ run_wc_java_lint() {
158
+ local bin="" timeout_s out_file err_file exit_code count
159
+
160
+ if ! bin=$(wc_java_lint_bin); then
161
+ return 9 # not installed -> caller falls back to the legacy chain
162
+ fi
163
+
164
+ timeout_s=$(resolve_wc_java_lint_timeout)
165
+ out_file=$(mktemp "${TMPDIR:-/tmp}/wc-java-lint.XXXXXX")
166
+ err_file="${out_file}.err"
167
+
168
+ # Primary contract call: the tool inspects the index itself.
169
+ # `|| exit_code=$?` keeps a non-zero verdict from killing the host when
170
+ # this module is sourced into a `set -e` consumer (repo convention,
171
+ # cf. gate-4.sh's `|| PRINCIPLES_EXIT=$?`).
172
+ exit_code=0
173
+ if command -v timeout >/dev/null 2>&1; then
174
+ timeout "$timeout_s" "$bin" check --staged --format json >"$out_file" 2>"$err_file" || exit_code=$?
175
+ else
176
+ echo " ⚠️ 'timeout' command not available - running wc-java-lint without a timeout guard" >&2
177
+ "$bin" check --staged --format json >"$out_file" 2>"$err_file" || exit_code=$?
178
+ fi
179
+
180
+ # Usage-error fallback (#507 AC-507-01-06): an older build without --staged
181
+ # answers with a usage message and exit>=2. Retry with the explicit staged
182
+ # .java list; any OTHER exit>=2 stays an execution error.
183
+ if [ "$exit_code" -ge 2 ] \
184
+ && grep -qiE 'unknown option|unrecognized option|^usage' "$err_file" "$out_file" 2>/dev/null; then
185
+ local staged_args=()
186
+ while IFS= read -r jf; do
187
+ [ -n "$jf" ] && staged_args+=("$jf")
188
+ done <<< "$(java_staged_files)"
189
+ if [ "${#staged_args[@]}" -gt 0 ]; then
190
+ exit_code=0
191
+ if command -v timeout >/dev/null 2>&1; then
192
+ timeout "$timeout_s" "$bin" check --format json "${staged_args[@]}" >"$out_file" 2>"$err_file" || exit_code=$?
193
+ else
194
+ "$bin" check --format json "${staged_args[@]}" >"$out_file" 2>"$err_file" || exit_code=$?
195
+ fi
196
+ fi
197
+ fi
198
+
199
+ if [ -s "$err_file" ]; then
200
+ sed 's/^/ /' "$err_file"
201
+ fi
202
+
203
+ case "$exit_code" in
204
+ 0)
205
+ if ! count=$(wc_java_json_violation_count "$out_file"); then
206
+ echo "❌ wc-java-lint output is not a usable violations JSON (fail-closed, #507 DR-001)"
207
+ rm -f "$out_file" "$err_file"
208
+ if [ "${XP_GATE_WC_JAVA_LINT:-}" = "soft" ]; then
209
+ # Unusable output IS an execution-class failure (the tool exists but
210
+ # its response cannot be trusted) -- same class as exit>=2, covered
211
+ # by the escape valve (#507 Delphi round-1 B-MAJOR-5).
212
+ echo "⚠️ XP_GATE_WC_JAVA_LINT=soft: unusable JSON downgraded to WARN (non-blocking, in scoring denominator)"
213
+ echo "XP_G1_REASON: wc-java-lint unusable JSON (soft downgrade)"
214
+ return 3
215
+ fi
216
+ return 2
217
+ fi
218
+ if [ "$count" -gt 0 ]; then
219
+ # Contract says exit 0 means clean; objects + 0 is a LYING tool, not a
220
+ # broken one. It claims success while reporting violations -- there is
221
+ # no trustworthy interpretation, so soft/report must NOT downgrade it
222
+ # (deliberately outside the escape valve; #507 Delphi round-1 B-MAJOR-5).
223
+ echo "❌ wc-java-lint exited 0 but reported $count violation object(s) - inconsistent response"
224
+ rm -f "$out_file" "$err_file"
225
+ return 2
226
+ fi
227
+ rm -f "$out_file" "$err_file"
228
+ echo "✅ wc-java-lint: clean"
229
+ return 0
230
+ ;;
231
+ 1)
232
+ if count=$(wc_java_json_violation_count "$out_file"); then
233
+ echo "❌ wc-java-lint: $count violation(s) in:"
234
+ wc_java_json_files "$out_file" | sed 's/^/ /'
235
+ else
236
+ echo "❌ wc-java-lint reported violations but the JSON was unusable"
237
+ fi
238
+ rm -f "$out_file" "$err_file"
239
+ if [ "${XP_GATE_WC_JAVA_LINT:-}" = "report" ]; then
240
+ # Rollout/grace mode (#507 Delphi round-1 C-MAJOR-3): a legacy repo
241
+ # adopting wc-java-lint carries pre-existing violations; hard-blocking
242
+ # every commit gives no migration path. report mode degrades REAL
243
+ # violations to non-blocking WARN -- pre-commit scores WARN in the
244
+ # denominator and an overall WARN verdict forbids a PASS (Delphi
245
+ # round-2 A-MAJOR-1: SKIP would have produced a fake 10/0 for a
246
+ # repo whose only Java check is suppressed), so teams burn down
247
+ # findings before switching back to enforcing.
248
+ echo "⚠️ XP_GATE_WC_JAVA_LINT=report: violations downgraded to WARN (rollout mode)"
249
+ echo "⚠️ Java code quality NOT enforcing - burn down the findings, then unset report mode"
250
+ echo "XP_G1_REASON: wc-java-lint report mode: ${count:-unknown} violation(s) suppressed"
251
+ return 3
252
+ fi
253
+ return 1
254
+ ;;
255
+ *)
256
+ rm -f "$out_file" "$err_file"
257
+ if [ "$exit_code" -eq 124 ]; then
258
+ echo "❌ wc-java-lint timed out after ${timeout_s}s"
259
+ else
260
+ echo "❌ wc-java-lint failed with exit code $exit_code"
261
+ fi
262
+ if [ "${XP_GATE_WC_JAVA_LINT:-}" = "soft" ]; then
263
+ echo "⚠️ XP_GATE_WC_JAVA_LINT=soft: execution failure downgraded to WARN (non-blocking, #507 DR-001 escape valve)"
264
+ echo "⚠️ Java static analysis NOT performed - the engine exists but is broken; do not treat this as a clean run"
265
+ # The reason crosses the command-substitution subshell boundary via a
266
+ # prefixed stdout line (export does NOT: the caller captures this
267
+ # function's output, so the subshell env is discarded -- #507 Delphi
268
+ # round-1 B-MAJOR-1). pre-commit parses and strips the line.
269
+ echo "XP_G1_REASON: wc-java-lint broken (soft downgrade)"
270
+ return 3
271
+ fi
272
+ return 2
273
+ ;;
274
+ esac
275
+ }
276
+
277
+ # ---------------------------------------------------------------------------
8
278
  _is_whalecloud_enabled() {
9
279
  # Check if whalecloud-java plugin is enabled
10
280
  [ -d "$WHALECLOUD_PLUGIN_DIR" ] && \
@@ -22,40 +292,74 @@ _detect_java_build() {
22
292
  fi
23
293
  }
24
294
 
25
- run_static_analysis() {
26
- local build_system
295
+ # ---------------------------------------------------------------------------
296
+ # Legacy fallback (#507 AC-507-01-03): only reached when wc-java-lint is not
297
+ # installed. Prints one WARN per missing plugin dir; if neither checkstyle
298
+ # nor pmd is available the verdict must NOT be PASS (exit 3, audit warn).
299
+ # ---------------------------------------------------------------------------
300
+ run_legacy_analysis() {
301
+ local build_system lint_tools=0 worst=0 rc=0
302
+
27
303
  build_system=$(_detect_java_build)
28
304
 
29
- if [ "$build_system" = "maven" ]; then
30
- mvn compile -q 2>&1 | grep -i "error\|fail" | sed -n '1,5p; 5q'
31
- if [ "${PIPESTATUS[0]}" -ne 0 ]; then
32
- echo "❌ Maven compilation failed"
33
- return 1
34
- fi
35
- elif [ "$build_system" = "gradle" ]; then
36
- gradle compileJava --quiet 2>&1 | grep -i "error\|fail" | sed -n '1,5p; 5q'
37
- if [ "${PIPESTATUS[0]}" -ne 0 ]; then
38
- echo "❌ Gradle compilation failed"
39
- return 1
40
- fi
305
+ if [ ! -d "$PLUGIN_DIR" ]; then
306
+ echo "⚠️ p3c-java plugin directory missing: $PLUGIN_DIR"
41
307
  fi
308
+ if [ ! -d "$WHALECLOUD_PLUGIN_DIR" ]; then
309
+ echo "⚠️ whalecloud-java plugin directory missing: $WHALECLOUD_PLUGIN_DIR"
310
+ fi
311
+
312
+ # Every engine's verdict is AGGREGATED (#507 blind review MAJOR-1): a tool
313
+ # that reports violations must never degrade to a silent PASS -- that was
314
+ # the exact anti-pattern this sprint exists to remove.
315
+ local out_file
316
+ out_file=$(mktemp "${TMPDIR:-/tmp}/java-legacy.XXXXXX")
42
317
 
43
318
  # CheckStyle with Google style (legacy fallback)
44
319
  if command -v checkstyle &>/dev/null; then
45
- checkstyle -c /google_checks.xml . 2>&1 | sed -n '1,20p; 20q'
320
+ lint_tools=$((lint_tools + 1))
321
+ rc=0
322
+ checkstyle -c /google_checks.xml . >"$out_file" 2>&1 || rc=$?
323
+ sed -n '1,20p' "$out_file"
324
+ [ "$rc" -ne 0 ] && worst=1
46
325
  fi
47
326
 
48
327
  # PMD error detection (legacy fallback)
49
328
  if command -v pmd &>/dev/null; then
50
- pmd check -d . -R category/java/errorprone.xml 2>&1 | sed -n '1,20p; 20q'
329
+ lint_tools=$((lint_tools + 1))
330
+ rc=0
331
+ pmd check -d . -R category/java/errorprone.xml >"$out_file" 2>&1 || rc=$?
332
+ sed -n '1,20p' "$out_file"
333
+ [ "$rc" -ne 0 ] && worst=1
334
+ fi
335
+ rm -f "$out_file"
336
+
337
+ if [ "$lint_tools" -eq 0 ]; then
338
+ echo "⚠️ No Java static-analysis engine available: wc-java-lint, checkstyle and pmd all missing"
339
+ echo "⚠️ Java code quality NOT verified - verdict is WARN, not PASS (#507 AC-507-01-03)"
340
+ echo " Install wc-java-lint (preferred) or checkstyle/pmd to restore verification"
341
+ _XP_JAVA_LEGACY_WARN=1
342
+ echo "XP_G1_REASON: legacy tools unavailable (warn)"
51
343
  fi
52
344
 
53
345
  # p3c-pmd check (Alibaba coding guidelines) — primary Java quality gate
54
- _run_p3c_check "$build_system"
346
+ # (`-Dpmd.failOnViolation=true` makes violations exit non-zero).
347
+ rc=0
348
+ _run_p3c_check "$build_system" || rc=$?
349
+ [ "$rc" -ne 0 ] && worst=1
55
350
 
56
351
  # WhaleCloud Java Coding Standards — overlay on top of p3c-pmd
57
- _run_whalecloud_check "$build_system"
352
+ rc=0
353
+ _run_whalecloud_check "$build_system" || rc=$?
354
+ [ "$rc" -ne 0 ] && worst=1
58
355
 
356
+ if [ "$worst" -ne 0 ]; then
357
+ echo " ❌ legacy Java analysis found violations"
358
+ return 1
359
+ fi
360
+ if [ "${_XP_JAVA_LEGACY_WARN:-0}" = "1" ]; then
361
+ return 3
362
+ fi
59
363
  return 0
60
364
  }
61
365
 
@@ -128,6 +432,21 @@ run_p3c_check() {
128
432
  _run_p3c_check "$(_detect_java_build)"
129
433
  }
130
434
 
435
+ # Legacy maven/gradle invocations are network-facing (plugin/rule resolution
436
+ # against remote repositories). Without a guard a cold cache can hang
437
+ # pre-commit for minutes (#507 Delphi round-1 C-MAJOR-2; round-2 extended the
438
+ # same guard to the Gradle legacy lint calls). Same timeout file as the
439
+ # primary engine: .xp-gate/wc-java-lint-timeout. Degrades safely when
440
+ # `timeout` is unavailable (rc 127 must never be mistaken for a tool verdict).
441
+ _legacy_build_run() {
442
+ if command -v timeout >/dev/null 2>&1; then
443
+ timeout "$(resolve_wc_java_lint_timeout)" "$@"
444
+ else
445
+ echo " ⚠️ 'timeout' command not available - legacy build-tool call runs unguarded" >&2
446
+ "$@"
447
+ fi
448
+ }
449
+
131
450
  _run_p3c_check() {
132
451
  local build_system="$1"
133
452
 
@@ -135,13 +454,20 @@ _run_p3c_check() {
135
454
 
136
455
  if [ "$build_system" = "maven" ]; then
137
456
  if grep -q '<id>xp-gate-p3c</id>' pom.xml 2>/dev/null; then
138
- # Profile already installed — use it
139
- mvn pmd:check -P xp-gate-p3c -Dpmd.failOnViolation=true 2>&1 | tail -30
457
+ # Profile already installed — use it (timeout-guarded: legacy maven
458
+ # resolves plugins/rulesets over the network, a cold cache must not
459
+ # hang pre-commit unbounded -- #507 Delphi round-1 C-MAJOR-2)
460
+ _legacy_build_run mvn pmd:check -P xp-gate-p3c -Dpmd.failOnViolation=true 2>&1 | tail -30
140
461
  return "${PIPESTATUS[0]}"
141
462
  else
142
- # Profile not installed — run inline with p3c-pmd dependency
143
- if command -v mvn &>/dev/null; then
144
- mvn pmd:check \
463
+ # Profile not installed. The inline ruleset paths (/rulesets/java/ali-*)
464
+ # resolve INSIDE the p3c-pmd jar — without that dependency in the pom,
465
+ # maven cannot resolve them and exits non-zero, which BLOCKed the commit
466
+ # on every real project without p3c configured (#507 Delphi round-1
467
+ # C-MAJOR-1). "Not configured" is an opt-out, not a violation: only run
468
+ # inline when the pom actually declares p3c-pmd.
469
+ if grep -q 'p3c-pmd' pom.xml 2>/dev/null; then
470
+ _legacy_build_run mvn pmd:check \
145
471
  -Dpmd.rulesets="/rulesets/java/ali-comment.xml,/rulesets/java/ali-concurrent.xml,/rulesets/java/ali-constant.xml,/rulesets/java/ali-exception.xml,/rulesets/java/ali-flowcontrol.xml,/rulesets/java/ali-naming.xml,/rulesets/java/ali-oop.xml,/rulesets/java/ali-orm.xml,/rulesets/java/ali-other.xml,/rulesets/java/ali-set.xml" \
146
472
  -Dpmd.failOnViolation=true \
147
473
  -DprintFailingErrors=true \
@@ -158,7 +484,8 @@ _run_p3c_check() {
158
484
  echo " ✅ p3c-pmd check passed"
159
485
  return 0
160
486
  else
161
- echo " ℹ️ Maven not available — Skipping p3c-pmd"
487
+ echo " ℹ️ p3c-pmd not configured in pom.xml — skipping (opt-in, not a violation)"
488
+ echo " To enable: bash $PLUGIN_DIR/scripts/install-maven-p3c.sh"
162
489
  return 0
163
490
  fi
164
491
  fi
@@ -166,7 +493,7 @@ _run_p3c_check() {
166
493
  elif [ "$build_system" = "gradle" ]; then
167
494
  if grep -q 'xp-gateP3cCheck\|p3c-pmd' build.gradle 2>/dev/null || \
168
495
  grep -q 'xp-gateP3cCheck\|p3c-pmd' build.gradle.kts 2>/dev/null; then
169
- gradle xp-gateP3cCheck --quiet 2>&1 | tail -20
496
+ _legacy_build_run gradle xp-gateP3cCheck --quiet 2>&1 | tail -20
170
497
  return "${PIPESTATUS[0]}"
171
498
  else
172
499
  echo " ℹ️ p3c-pmd not configured in Gradle build"
@@ -190,7 +517,8 @@ _run_whalecloud_check() {
190
517
 
191
518
  if [ "$build_system" = "maven" ]; then
192
519
  if grep -q '<id>xp-gate-whalecloud-java</id>' pom.xml 2>/dev/null; then
193
- mvn pmd:check checkstyle:check spotbugs:check \
520
+ # timeout-guarded, same rationale as the p3c branch (C-MAJOR-2)
521
+ _legacy_build_run mvn pmd:check checkstyle:check spotbugs:check \
194
522
  -P xp-gate-whalecloud-java -Dpmd.failOnViolation=true \
195
523
  2>&1 | tail -30
196
524
  return "${PIPESTATUS[0]}"
@@ -203,7 +531,7 @@ _run_whalecloud_check() {
203
531
  elif [ "$build_system" = "gradle" ]; then
204
532
  if grep -q 'xp-gateWhalecloudCheck' build.gradle 2>/dev/null || \
205
533
  grep -q 'xp-gateWhalecloudCheck' build.gradle.kts 2>/dev/null; then
206
- gradle xp-gateWhalecloudCheck --quiet 2>&1 | tail -20
534
+ _legacy_build_run gradle xp-gateWhalecloudCheck --quiet 2>&1 | tail -20
207
535
  return "${PIPESTATUS[0]}"
208
536
  else
209
537
  echo " ⚠️ whalecloud-java not configured in Gradle build"
@@ -218,3 +546,21 @@ _run_whalecloud_check() {
218
546
  run_whalecloud_check() {
219
547
  _run_whalecloud_check "$(_detect_java_build)"
220
548
  }
549
+
550
+ # ---------------------------------------------------------------------------
551
+ # GATE 1 entry (#507 S1). wc-java-lint first; legacy chain only on discovery
552
+ # miss. The old direct mvn/gradle compile here moved to Build Integrity (S5).
553
+ # ---------------------------------------------------------------------------
554
+ run_static_analysis() {
555
+ local rc=0
556
+ # `|| rc=$?` (not a bare call): under a set -e consumer a non-zero verdict
557
+ # would otherwise abort the host hook instead of returning the verdict code.
558
+ run_wc_java_lint || rc=$?
559
+ if [ "$rc" -eq 9 ]; then
560
+ echo "ℹ️ wc-java-lint not found - falling back to legacy Java analysis"
561
+ rc=0
562
+ run_legacy_analysis || rc=$?
563
+ return "$rc"
564
+ fi
565
+ return "$rc"
566
+ }
package/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
- CCN_THRESHOLD=5
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
- CC_OUTPUT=$($LIZARD_PATH -C $CCN_THRESHOLD $CC_FILES 2>&1 || true)
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/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
  #