@boyingliu01/xp-gate 0.30.2 → 0.30.4
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/iac.sh +45 -4
- package/adapters/java.sh +82 -16
- package/adapters/powershell.sh +70 -9
- package/gate-10.sh +110 -13
- package/gate-3.sh +17 -0
- package/gate-4.sh +12 -0
- package/gate-7.sh +4 -0
- package/hooks/gate-10.sh +110 -13
- package/hooks/gate-3.sh +17 -0
- package/hooks/gate-4.sh +12 -0
- package/hooks/gate-7.sh +4 -0
- package/hooks/hook-module-resolution.sh +14 -0
- package/hooks/lib/coverage-total.sh +1 -1
- package/hooks/pre-commit +138 -17
- package/hooks/pre-push +150 -9
- package/hooks/sprint-gate.sh +81 -2
- package/lib/__tests__/hook-health.test.js +85 -0
- package/lib/coverage-total.sh +92 -0
- package/lib/doctor.js +44 -0
- package/lib/hook-health.js +121 -0
- package/lib/mattpocock-license-verdict.json +1 -1
- package/mock-policy/AGENTS.md +2 -2
- package/mutation/AGENTS.md +2 -2
- package/mutation/__tests__/gate-m-survivors.test.ts +133 -0
- package/mutation/gate-m.ts +114 -11
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
- package/principles/AGENTS.md +2 -2
- package/skills/delphi-review/AGENTS.md +2 -2
- package/skills/sprint-flow/AGENTS.md +2 -2
- package/skills/test-specification-alignment/AGENTS.md +2 -2
- package/sprint-gate.sh +81 -2
package/gate-4.sh
CHANGED
|
@@ -56,11 +56,15 @@ GATE_4_START=$(gate_start_ms)
|
|
|
56
56
|
GATE_4_STATUS=""
|
|
57
57
|
# #537: every non-run records WHY (no files / tool missing / unsupported lang)
|
|
58
58
|
# so the quality report can distinguish "not applicable" from "not covered".
|
|
59
|
+
# Consumed by the sourcing parent, not here (see #548).
|
|
60
|
+
# shellcheck disable=SC2034
|
|
59
61
|
GATE_4_REASON=""
|
|
60
62
|
|
|
61
63
|
if [ "$PROJECT_LANG" = "documentation-only" ]; then
|
|
62
64
|
echo "⏭️ SKIPPED - Principles check (documentation project)."
|
|
63
65
|
GATE_4_STATUS="SKIP"
|
|
66
|
+
# Consumed by the sourcing parent, not here (see #548).
|
|
67
|
+
# shellcheck disable=SC2034
|
|
64
68
|
GATE_4_REASON="documentation_project"
|
|
65
69
|
|
|
66
70
|
else
|
|
@@ -156,6 +160,8 @@ else
|
|
|
156
160
|
echo "⚠️ Warning: Principles checker execution failed"
|
|
157
161
|
echo "⏭️ SKIPPED - Principles check (execution issue)"
|
|
158
162
|
GATE_4_STATUS="SKIP"
|
|
163
|
+
# Consumed by the sourcing parent, not here (see #548).
|
|
164
|
+
# shellcheck disable=SC2034
|
|
159
165
|
GATE_4_REASON="checker_execution_error"
|
|
160
166
|
rm -f "$PRINCIPLES_JSON"
|
|
161
167
|
else
|
|
@@ -191,17 +197,23 @@ else
|
|
|
191
197
|
echo "ℹ️ npx/tsx not available - skipping principles check"
|
|
192
198
|
echo "⏭️ SKIPPED - Principles check (no Node.js/tsx)"
|
|
193
199
|
GATE_4_STATUS="SKIP"
|
|
200
|
+
# Consumed by the sourcing parent, not here (see #548).
|
|
201
|
+
# shellcheck disable=SC2034
|
|
194
202
|
GATE_4_REASON="tool_not_installed"
|
|
195
203
|
fi
|
|
196
204
|
else
|
|
197
205
|
echo "ℹ️ Principles checker not found in project - skipping"
|
|
198
206
|
echo "⏭️ SKIPPED - Principles check (checker not in project)"
|
|
199
207
|
GATE_4_STATUS="SKIP"
|
|
208
|
+
# Consumed by the sourcing parent, not here (see #548).
|
|
209
|
+
# shellcheck disable=SC2034
|
|
200
210
|
GATE_4_REASON="checker_not_found"
|
|
201
211
|
fi
|
|
202
212
|
else
|
|
203
213
|
echo "⏭️ SKIPPED - Principles check (no matching source files changed)"
|
|
204
214
|
GATE_4_STATUS="SKIP"
|
|
215
|
+
# Consumed by the sourcing parent, not here (see #548).
|
|
216
|
+
# shellcheck disable=SC2034
|
|
205
217
|
GATE_4_REASON="no_matching_files_changed"
|
|
206
218
|
fi
|
|
207
219
|
fi
|
package/gate-7.sh
CHANGED
|
@@ -12,11 +12,15 @@ GATE_7_START=$(gate_start_ms)
|
|
|
12
12
|
IAC_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null | grep -E "\.(tf|yaml|yml)$|Dockerfile" || true)
|
|
13
13
|
# #536: scanned_files makes a trivial PASS (nothing to scan) auditable in the
|
|
14
14
|
# quality report instead of reading like a covered security check.
|
|
15
|
+
# Consumed by the sourcing parent, not here (see #548).
|
|
16
|
+
# shellcheck disable=SC2034
|
|
15
17
|
GATE_7_SCANNED_FILES=0
|
|
16
18
|
if [ -z "$IAC_FILES" ]; then
|
|
17
19
|
echo "✅ PASSED - No IaC files detected in changes."
|
|
18
20
|
GATE_7_STATUS="PASS"
|
|
19
21
|
else
|
|
22
|
+
# Consumed by the sourcing parent, not here (see #548).
|
|
23
|
+
# shellcheck disable=SC2034
|
|
20
24
|
GATE_7_SCANNED_FILES=$(printf '%s\n' "$IAC_FILES" | sed -n '/./p' | wc -l | tr -d '[:space:]')
|
|
21
25
|
# Run IaC adapter. Resolution follows the documented 3-tier convention
|
|
22
26
|
# (adapter-common.sh): global ~/.config/xp-gate/adapters -> project
|
package/hooks/gate-10.sh
CHANGED
|
@@ -12,7 +12,20 @@ GATE_10_START=$(gate_start_ms)
|
|
|
12
12
|
GATE_10_STATUS=""
|
|
13
13
|
# #536: audit trail for "did this commit actually get scanned?". The report
|
|
14
14
|
# surfaces reason + scanned_files so a non-scan never reads as a clean PASS.
|
|
15
|
+
#
|
|
16
|
+
# GATE_10_REASON and GATE_10_SCANNED_FILES are consumed by the sourcing parent
|
|
17
|
+
# (pre-commit builds its report JSON from them after `source gate-10.sh`), not
|
|
18
|
+
# inside this file. The linter checks one file at a time and cannot see the
|
|
19
|
+
# consumer, so each assignment is flagged SC2034 "appears unused" -- which
|
|
20
|
+
# blocks Gate 1 for any repo that vendors this adapter, because that repo's own
|
|
21
|
+
# lint pass runs over the vendored copy (#548). Each assignment carries a
|
|
22
|
+
# scoped directive with that reason stated at the site.
|
|
23
|
+
#
|
|
24
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
25
|
+
# shellcheck disable=SC2034
|
|
15
26
|
GATE_10_REASON=""
|
|
27
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
28
|
+
# shellcheck disable=SC2034
|
|
16
29
|
GATE_10_SCANNED_FILES=0
|
|
17
30
|
|
|
18
31
|
# Semgrep availability check
|
|
@@ -29,14 +42,21 @@ if [ -z "$SEMGREP_CMD" ]; then
|
|
|
29
42
|
echo " Pre-cache rules: semgrep --config=p/security-audit"
|
|
30
43
|
echo " Gate 10: SAST Security (WARN, semgrep not installed)"
|
|
31
44
|
GATE_10_STATUS="WARN"
|
|
45
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
46
|
+
# shellcheck disable=SC2034
|
|
32
47
|
GATE_10_REASON="tool_not_installed"
|
|
33
48
|
else
|
|
34
|
-
# Get staged files filtered to Semgrep-supported languages
|
|
35
|
-
|
|
49
|
+
# Get staged files filtered to Semgrep-supported languages.
|
|
50
|
+
# #547 finding 3: ps1|psm1 were missing, so a PowerShell-only commit never
|
|
51
|
+
# reached semgrep even when Gate 10 ran -- the SAST gate was simply absent
|
|
52
|
+
# for that language.
|
|
53
|
+
SEMGREP_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null | grep -E '\.(ts|tsx|js|jsx|py|go|java|c|cpp|cs|rb|php|scala|swift|ps1|psm1)$' || true)
|
|
36
54
|
|
|
37
55
|
if [ -z "$SEMGREP_FILES" ]; then
|
|
38
56
|
echo " ⏭️ SKIPPED - SAST (no supported language files changed)"
|
|
39
57
|
GATE_10_STATUS="SKIP"
|
|
58
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
59
|
+
# shellcheck disable=SC2034
|
|
40
60
|
GATE_10_REASON="no_supported_files"
|
|
41
61
|
else
|
|
42
62
|
# One path per line into an array. Quoting the newline-joined list would hand
|
|
@@ -71,11 +91,27 @@ else
|
|
|
71
91
|
[ -n "$_semgrep_file" ] || continue
|
|
72
92
|
SEMGREP_FILE_COUNT=$((SEMGREP_FILE_COUNT + 1))
|
|
73
93
|
SEMGREP_ARGS+=("--include=${_semgrep_file##*/}")
|
|
74
|
-
|
|
75
|
-
#
|
|
76
|
-
|
|
94
|
+
# A repo-root file has no slash at all, so "%/*" returns it unchanged and
|
|
95
|
+
# the old comment's assumption (`"./x.ts" strips to "./"`) never held:
|
|
96
|
+
# the target became the FILE path dressed as a directory ("./x.ts").
|
|
97
|
+
# Strip only when a slash is present; otherwise the target is the repo
|
|
98
|
+
# root, spelled ".". Scanning "." widens the scan to the whole tree, which
|
|
99
|
+
# is the safe side (see the note above) and is what a root-level staged
|
|
100
|
+
# file requires in order to be analysed at all.
|
|
101
|
+
case "$_semgrep_file" in
|
|
102
|
+
*/*) _semgrep_dir="./${_semgrep_file%/*}" ;;
|
|
103
|
+
*) _semgrep_dir="." ;;
|
|
104
|
+
esac
|
|
105
|
+
# #558: dedup must match WHOLE lines. The previous `case "$SEEN" in
|
|
106
|
+
# *"$_semgrep_dir"*)` was an unanchored substring test: once
|
|
107
|
+
# ./fastdl/tests was recorded, the parent ./fastdl matched it and was
|
|
108
|
+
# never added as a target, so a staged fastdl/a.py contributed an
|
|
109
|
+
# --include= with no directory to scan it in. Nothing errored and nothing
|
|
110
|
+
# was reported -- the file was simply not analysed, which for a security
|
|
111
|
+
# gate is the worst outcome: a silent net loss that looks like a pass.
|
|
112
|
+
# Sibling prefixes (src/api vs src/app) collapsed the same way.
|
|
77
113
|
case "$SEMGREP_DIRS_SEEN" in
|
|
78
|
-
|
|
114
|
+
*$'\n'"$_semgrep_dir"$'\n'*) : ;;
|
|
79
115
|
*) SEMGREP_ARGS+=("$_semgrep_dir"); SEMGREP_DIRS_SEEN="$SEMGREP_DIRS_SEEN$_semgrep_dir"$'\n' ;;
|
|
80
116
|
esac
|
|
81
117
|
done <<EOF
|
|
@@ -91,6 +127,8 @@ EOF
|
|
|
91
127
|
echo " Install node (https://nodejs.org); without it SAST findings cannot be classified"
|
|
92
128
|
echo " ⏭️ SKIPPED - SAST (node not installed)"
|
|
93
129
|
GATE_10_STATUS="SKIP"
|
|
130
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
131
|
+
# shellcheck disable=SC2034
|
|
94
132
|
GATE_10_REASON="tool_not_installed"
|
|
95
133
|
else
|
|
96
134
|
|
|
@@ -109,9 +147,41 @@ EOF
|
|
|
109
147
|
# code is demoted to "did the tool itself run". --error stays OFF: it would
|
|
110
148
|
# add nothing the JSON does not already provide, and keeping it off leaves
|
|
111
149
|
# failure exit codes unambiguous for the #475 diagnostics path.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
150
|
+
# #559: bound the run. Without a timeout, a wedged semgrep blocks the
|
|
151
|
+
# invocation for as long as its process lives; the fail-closed verdict logic
|
|
152
|
+
# below is correct but unreachable, so the hook hangs instead of judging.
|
|
153
|
+
# A gate that can wedge an interactive `git commit` gets bypassed with
|
|
154
|
+
# --no-verify, which is worse than any verdict it could have produced.
|
|
155
|
+
# `-k` after the primary signal: pysemgrep spinning in a syscall ignores
|
|
156
|
+
# SIGTERM, and the shipped 0.29 code used the same `-k 10` shape.
|
|
157
|
+
SEMGREP_TIMEOUT_S="${XP_GATE_SEMGREP_TIMEOUT:-180}"
|
|
158
|
+
SEMGREP_TIMED_OUT=false
|
|
159
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
160
|
+
SEMGREP_STDOUT_FILE=$(mktemp)
|
|
161
|
+
SEMGREP_OUTPUT=$(timeout -k 10 "$SEMGREP_TIMEOUT_S" \
|
|
162
|
+
"$SEMGREP_CMD" scan --config=p/security-audit --json --disable-version-check \
|
|
163
|
+
"${SEMGREP_ARGS[@]}" 2>&1 >"$SEMGREP_STDOUT_FILE")
|
|
164
|
+
SEMGREP_EXIT=$?
|
|
165
|
+
# 124 = timeout's own "I gave up" code. 137 = 128+SIGKILL, which is what
|
|
166
|
+
# `timeout -k` returns when the child ignored SIGTERM and had to be
|
|
167
|
+
# killed -- measured with a semgrep stand-in that traps TERM. Both mean
|
|
168
|
+
# "bounded, no verdict", so both must be reported as a timeout; checking
|
|
169
|
+
# only 124 told the user their semgrep was unparseable when in fact the
|
|
170
|
+
# scan had been cut off.
|
|
171
|
+
case "$SEMGREP_EXIT" in
|
|
172
|
+
124|137) SEMGREP_TIMED_OUT=true ;;
|
|
173
|
+
esac
|
|
174
|
+
else
|
|
175
|
+
# No `timeout` on this platform (stock macOS). Say so: an unbounded run
|
|
176
|
+
# nobody mentioned is exactly how #559 stayed invisible for a whole
|
|
177
|
+
# release. The scan still runs -- refusing to scan would be a worse
|
|
178
|
+
# trade -- but the unbounded window is now stated.
|
|
179
|
+
echo " ⚠️ 'timeout' command not available — semgrep runs UNBOUNDED (install coreutils or set up gtimeout)"
|
|
180
|
+
SEMGREP_STDOUT_FILE=$(mktemp)
|
|
181
|
+
SEMGREP_OUTPUT=$("$SEMGREP_CMD" scan --config=p/security-audit --json --disable-version-check \
|
|
182
|
+
"${SEMGREP_ARGS[@]}" 2>&1 >"$SEMGREP_STDOUT_FILE")
|
|
183
|
+
SEMGREP_EXIT=$?
|
|
184
|
+
fi
|
|
115
185
|
|
|
116
186
|
# One node pass over the JSON: first line = verdict counts, remaining lines =
|
|
117
187
|
# finding details. The severity vocabulary is the UNION of the two scales
|
|
@@ -153,7 +223,18 @@ console.log("");});
|
|
|
153
223
|
if [ "$V_PARSE" != "OK" ] || [ -z "$V_RESULTS" ] || [ -z "$V_ERRORS" ] || [ -z "$V_BLOCKING" ] || [ -z "$V_WARNINGS" ]; then
|
|
154
224
|
# Unparseable output: nothing the exit code claims can be trusted (#518).
|
|
155
225
|
# Fail closed — SKIP with diagnostics, never a PASS.
|
|
156
|
-
|
|
226
|
+
#
|
|
227
|
+
# #559: name a timeout as a timeout. The generic "unparseable" message
|
|
228
|
+
# would be true but useless here -- it points at the JSON parser when the
|
|
229
|
+
# real problem is that the scan was cut off. Same verdict (SKIP), but a
|
|
230
|
+
# diagnosis the reader can act on.
|
|
231
|
+
if [ "$SEMGREP_TIMED_OUT" = true ]; then
|
|
232
|
+
echo " ⏱️ semgrep exceeded ${SEMGREP_TIMEOUT_S}s and was killed — no verdict is possible"
|
|
233
|
+
echo " This is NOT a finding and NOT a clean scan. Raise XP_GATE_SEMGREP_TIMEOUT"
|
|
234
|
+
echo " if the repo legitimately takes longer; investigate if semgrep itself is wedged."
|
|
235
|
+
else
|
|
236
|
+
echo " ⚠️ semgrep output unparseable or verdict fields missing (exit code ${SEMGREP_EXIT}) — cannot determine verdict"
|
|
237
|
+
fi
|
|
157
238
|
if [ -n "$SEMGREP_OUTPUT" ]; then
|
|
158
239
|
echo " ── what semgrep reported on stderr ──"
|
|
159
240
|
printf '%s\n' "$SEMGREP_OUTPUT" | tail -10 | sed 's/^/ /'
|
|
@@ -162,9 +243,19 @@ console.log("");});
|
|
|
162
243
|
echo " ── what semgrep wrote to stdout (tail) ──"
|
|
163
244
|
printf '%s\n' "$SEMGREP_STDOUT_TAIL" | sed 's/^/ /'
|
|
164
245
|
fi
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
246
|
+
if [ "$SEMGREP_TIMED_OUT" = true ]; then
|
|
247
|
+
echo " ⏭️ SKIPPED - SAST (semgrep timed out after ${SEMGREP_TIMEOUT_S}s)"
|
|
248
|
+
GATE_10_STATUS="SKIP"
|
|
249
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
250
|
+
# shellcheck disable=SC2034
|
|
251
|
+
GATE_10_REASON="semgrep_timeout"
|
|
252
|
+
else
|
|
253
|
+
echo " ⏭️ SKIPPED - SAST (semgrep output unparseable)"
|
|
254
|
+
GATE_10_STATUS="SKIP"
|
|
255
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
256
|
+
# shellcheck disable=SC2034
|
|
257
|
+
GATE_10_REASON="output_unparseable"
|
|
258
|
+
fi
|
|
168
259
|
elif [ "$V_RESULTS" -eq 0 ] && { [ "$V_ERRORS" -gt 0 ] || [ "$SEMGREP_EXIT" -ne 0 ]; }; then
|
|
169
260
|
# Nothing was scanned to a verdict: semgrep either failed outright
|
|
170
261
|
# (nonzero exit) or completed but reported scan errors for every input
|
|
@@ -178,6 +269,8 @@ console.log("");});
|
|
|
178
269
|
fi
|
|
179
270
|
echo " ⏭️ SKIPPED - SAST (semgrep runtime error)"
|
|
180
271
|
GATE_10_STATUS="SKIP"
|
|
272
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
273
|
+
# shellcheck disable=SC2034
|
|
181
274
|
GATE_10_REASON="semgrep_runtime_error"
|
|
182
275
|
elif [ "$V_BLOCKING" -gt 0 ]; then
|
|
183
276
|
echo ""
|
|
@@ -192,9 +285,13 @@ console.log("");});
|
|
|
192
285
|
echo "$FINDING_DETAILS"
|
|
193
286
|
echo " Run 'semgrep scan --config=p/security-audit' to review all findings."
|
|
194
287
|
GATE_10_STATUS="FAIL"
|
|
288
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
289
|
+
# shellcheck disable=SC2034
|
|
195
290
|
GATE_10_SCANNED_FILES=$SEMGREP_FILE_COUNT
|
|
196
291
|
exit 1
|
|
197
292
|
else
|
|
293
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
294
|
+
# shellcheck disable=SC2034
|
|
198
295
|
GATE_10_SCANNED_FILES=$SEMGREP_FILE_COUNT
|
|
199
296
|
echo ""
|
|
200
297
|
echo " ✅ PASSED - No blocking vulnerabilities (files scanned: $SEMGREP_FILE_COUNT)"
|
package/hooks/gate-3.sh
CHANGED
|
@@ -8,11 +8,15 @@
|
|
|
8
8
|
2>&1 echo "→ Gate 3: Cyclomatic complexity..."
|
|
9
9
|
GATE_3_START=$(gate_start_ms)
|
|
10
10
|
# #537: every non-run records WHY so the report can say why a gate is SKIP.
|
|
11
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
12
|
+
# shellcheck disable=SC2034
|
|
11
13
|
GATE_3_REASON=""
|
|
12
14
|
|
|
13
15
|
if [ "$PROJECT_LANG" = "documentation-only" ]; then
|
|
14
16
|
echo "⏭️ SKIPPED - Complexity (documentation project)."
|
|
15
17
|
GATE_3_STATUS="SKIP"
|
|
18
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
19
|
+
# shellcheck disable=SC2034
|
|
16
20
|
GATE_3_REASON="documentation_project"
|
|
17
21
|
|
|
18
22
|
elif [ "$PROJECT_LANG" = "powershell" ]; then
|
|
@@ -21,6 +25,8 @@ elif [ "$PROJECT_LANG" = "powershell" ]; then
|
|
|
21
25
|
GATE_3_STATUS="SKIP"
|
|
22
26
|
# #537: a PowerShell-primary repo was invisible to this dimension while the
|
|
23
27
|
# score implied full coverage — say exactly why nothing ran.
|
|
28
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
29
|
+
# shellcheck disable=SC2034
|
|
24
30
|
GATE_3_REASON="no_powershell_tool"
|
|
25
31
|
|
|
26
32
|
else
|
|
@@ -159,7 +165,10 @@ else
|
|
|
159
165
|
# empty stdin — the verdict comes back empty and Gate 3 passes on zero
|
|
160
166
|
# rows (measured while wiring #544: same input passed or blocked
|
|
161
167
|
# depending only on where the prefix sat).
|
|
168
|
+
# Consumed by evaluate_ccn_ratchet in the sourced lib, not here.
|
|
169
|
+
# shellcheck disable=SC2034
|
|
162
170
|
CCN_WARN_THRESHOLD="$CCN_THRESHOLD"
|
|
171
|
+
# shellcheck disable=SC2034
|
|
163
172
|
CCN_BLOCK_THRESHOLD=$((CCN_THRESHOLD * 2))
|
|
164
173
|
RATCHET_VERDICT=$(printf '%s\n' "$CC_CSV" | evaluate_ccn_ratchet ".ccn-baseline.json")
|
|
165
174
|
CC_VIOLATIONS=$(printf '%s\n' "$RATCHET_VERDICT" | grep -c '^BLOCK ' || true)
|
|
@@ -205,6 +214,8 @@ else
|
|
|
205
214
|
END { exit(found ? 0 : 1) }'; then
|
|
206
215
|
echo "⚠️ WARN - lizard produced no parsable results; complexity not verified"
|
|
207
216
|
GATE_3_STATUS="WARN"
|
|
217
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
218
|
+
# shellcheck disable=SC2034
|
|
208
219
|
GATE_3_REASON="lizard_output_unparsable"
|
|
209
220
|
elif [ "$CC_WARNINGS" -gt 0 ]; then
|
|
210
221
|
echo "$CC_OUTPUT"
|
|
@@ -212,6 +223,8 @@ else
|
|
|
212
223
|
echo "❌ BLOCKED - $CC_WARNINGS functions with CCN > $CCN_THRESHOLD found."
|
|
213
224
|
echo "Refactor high-complexity functions to keep below $CCN_THRESHOLD complexity."
|
|
214
225
|
GATE_3_STATUS="BLOCK"
|
|
226
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
227
|
+
# shellcheck disable=SC2034
|
|
215
228
|
GATE_3_REASON="ccn_over_threshold"
|
|
216
229
|
exit 1
|
|
217
230
|
else
|
|
@@ -222,6 +235,8 @@ else
|
|
|
222
235
|
else
|
|
223
236
|
echo "⏭️ SKIPPED - Complexity (no source files to check)."
|
|
224
237
|
GATE_3_STATUS="SKIP"
|
|
238
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
239
|
+
# shellcheck disable=SC2034
|
|
225
240
|
GATE_3_REASON="no_source_files_changed"
|
|
226
241
|
fi
|
|
227
242
|
else
|
|
@@ -229,6 +244,8 @@ else
|
|
|
229
244
|
echo " Install with: pip install --user lizard"
|
|
230
245
|
echo " Gate 3: Complexity check (WARN, tool not available)"
|
|
231
246
|
GATE_3_STATUS="WARN"
|
|
247
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
248
|
+
# shellcheck disable=SC2034
|
|
232
249
|
GATE_3_REASON="tool_not_installed"
|
|
233
250
|
fi
|
|
234
251
|
fi
|
package/hooks/gate-4.sh
CHANGED
|
@@ -56,11 +56,15 @@ GATE_4_START=$(gate_start_ms)
|
|
|
56
56
|
GATE_4_STATUS=""
|
|
57
57
|
# #537: every non-run records WHY (no files / tool missing / unsupported lang)
|
|
58
58
|
# so the quality report can distinguish "not applicable" from "not covered".
|
|
59
|
+
# Consumed by the sourcing parent, not here (see #548).
|
|
60
|
+
# shellcheck disable=SC2034
|
|
59
61
|
GATE_4_REASON=""
|
|
60
62
|
|
|
61
63
|
if [ "$PROJECT_LANG" = "documentation-only" ]; then
|
|
62
64
|
echo "⏭️ SKIPPED - Principles check (documentation project)."
|
|
63
65
|
GATE_4_STATUS="SKIP"
|
|
66
|
+
# Consumed by the sourcing parent, not here (see #548).
|
|
67
|
+
# shellcheck disable=SC2034
|
|
64
68
|
GATE_4_REASON="documentation_project"
|
|
65
69
|
|
|
66
70
|
else
|
|
@@ -156,6 +160,8 @@ else
|
|
|
156
160
|
echo "⚠️ Warning: Principles checker execution failed"
|
|
157
161
|
echo "⏭️ SKIPPED - Principles check (execution issue)"
|
|
158
162
|
GATE_4_STATUS="SKIP"
|
|
163
|
+
# Consumed by the sourcing parent, not here (see #548).
|
|
164
|
+
# shellcheck disable=SC2034
|
|
159
165
|
GATE_4_REASON="checker_execution_error"
|
|
160
166
|
rm -f "$PRINCIPLES_JSON"
|
|
161
167
|
else
|
|
@@ -191,17 +197,23 @@ else
|
|
|
191
197
|
echo "ℹ️ npx/tsx not available - skipping principles check"
|
|
192
198
|
echo "⏭️ SKIPPED - Principles check (no Node.js/tsx)"
|
|
193
199
|
GATE_4_STATUS="SKIP"
|
|
200
|
+
# Consumed by the sourcing parent, not here (see #548).
|
|
201
|
+
# shellcheck disable=SC2034
|
|
194
202
|
GATE_4_REASON="tool_not_installed"
|
|
195
203
|
fi
|
|
196
204
|
else
|
|
197
205
|
echo "ℹ️ Principles checker not found in project - skipping"
|
|
198
206
|
echo "⏭️ SKIPPED - Principles check (checker not in project)"
|
|
199
207
|
GATE_4_STATUS="SKIP"
|
|
208
|
+
# Consumed by the sourcing parent, not here (see #548).
|
|
209
|
+
# shellcheck disable=SC2034
|
|
200
210
|
GATE_4_REASON="checker_not_found"
|
|
201
211
|
fi
|
|
202
212
|
else
|
|
203
213
|
echo "⏭️ SKIPPED - Principles check (no matching source files changed)"
|
|
204
214
|
GATE_4_STATUS="SKIP"
|
|
215
|
+
# Consumed by the sourcing parent, not here (see #548).
|
|
216
|
+
# shellcheck disable=SC2034
|
|
205
217
|
GATE_4_REASON="no_matching_files_changed"
|
|
206
218
|
fi
|
|
207
219
|
fi
|
package/hooks/gate-7.sh
CHANGED
|
@@ -12,11 +12,15 @@ GATE_7_START=$(gate_start_ms)
|
|
|
12
12
|
IAC_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null | grep -E "\.(tf|yaml|yml)$|Dockerfile" || true)
|
|
13
13
|
# #536: scanned_files makes a trivial PASS (nothing to scan) auditable in the
|
|
14
14
|
# quality report instead of reading like a covered security check.
|
|
15
|
+
# Consumed by the sourcing parent, not here (see #548).
|
|
16
|
+
# shellcheck disable=SC2034
|
|
15
17
|
GATE_7_SCANNED_FILES=0
|
|
16
18
|
if [ -z "$IAC_FILES" ]; then
|
|
17
19
|
echo "✅ PASSED - No IaC files detected in changes."
|
|
18
20
|
GATE_7_STATUS="PASS"
|
|
19
21
|
else
|
|
22
|
+
# Consumed by the sourcing parent, not here (see #548).
|
|
23
|
+
# shellcheck disable=SC2034
|
|
20
24
|
GATE_7_SCANNED_FILES=$(printf '%s\n' "$IAC_FILES" | sed -n '/./p' | wc -l | tr -d '[:space:]')
|
|
21
25
|
# Run IaC adapter. Resolution follows the documented 3-tier convention
|
|
22
26
|
# (adapter-common.sh): global ~/.config/xp-gate/adapters -> project
|
|
@@ -7,12 +7,22 @@
|
|
|
7
7
|
# is its shell projection. Editing it by hand makes the hook disagree with
|
|
8
8
|
# doctor.js, which reads the JS module directly — the exact divergence
|
|
9
9
|
# #495/#500 were about.
|
|
10
|
+
#
|
|
11
|
+
# Every HOOK_* constant below is read by the sourcing parent, not by this
|
|
12
|
+
# file. A linter checking this file alone cannot see that consumer, so each
|
|
13
|
+
# declaration carries a scoped directive with the reason stated at the site
|
|
14
|
+
# (#548 -- the same false SC2034 used to block consumer repos that vendor
|
|
15
|
+
# these hook modules). The directives are generated, not hand-added.
|
|
10
16
|
|
|
11
17
|
# Probe file used to pick the gate tier.
|
|
18
|
+
# Consumed by the sourcing parent, not here.
|
|
19
|
+
# shellcheck disable=SC2034
|
|
12
20
|
HOOK_GATE_PROBE="gate-3.sh"
|
|
13
21
|
|
|
14
22
|
# ADAPTER_DIR cascade, highest priority first.
|
|
15
23
|
# Each entry is "tier_id|base_var|probe_file".
|
|
24
|
+
# Consumed by the sourcing parent, not here.
|
|
25
|
+
# shellcheck disable=SC2034
|
|
16
26
|
HOOK_ADAPTER_DIR_TIERS=(
|
|
17
27
|
'global|GLOBAL_ADAPTER_DIR|adapter-common.sh'
|
|
18
28
|
'project|PROJECT_GITHOOKS|adapter-common.sh'
|
|
@@ -21,6 +31,8 @@ HOOK_ADAPTER_DIR_TIERS=(
|
|
|
21
31
|
|
|
22
32
|
# Adapter resolution tiers, highest priority first.
|
|
23
33
|
# Each entry is "tier_id|base_var|template" with {name} as the basename.
|
|
34
|
+
# Consumed by the sourcing parent, not here.
|
|
35
|
+
# shellcheck disable=SC2034
|
|
24
36
|
HOOK_ADAPTER_TIERS=(
|
|
25
37
|
'adapter_flat|ADAPTER_DIR|{name}.sh'
|
|
26
38
|
'adapter_nested|ADAPTER_DIR|adapters/{name}.sh'
|
|
@@ -32,6 +44,8 @@ HOOK_ADAPTER_TIERS=(
|
|
|
32
44
|
# Semantics differ from adapter tiers: each entry names the DIRECTORY that
|
|
33
45
|
# would hold the gate probe, so a match yields the directory (GATE_DIR), not
|
|
34
46
|
# the probe file itself. Entry format: "tier_id|base_var|(empty suffix)".
|
|
47
|
+
# Consumed by the sourcing parent, not here.
|
|
48
|
+
# shellcheck disable=SC2034
|
|
35
49
|
HOOK_GATE_TIERS=(
|
|
36
50
|
'gate_adapter_dir|ADAPTER_DIR|'
|
|
37
51
|
'gate_project|PROJECT_GITHOOKS|'
|
|
@@ -70,7 +70,7 @@ enforce_python_coverage() {
|
|
|
70
70
|
fi
|
|
71
71
|
|
|
72
72
|
if [ -n "$COVERAGE_PERCENT" ]; then
|
|
73
|
-
if [ "$COVERAGE_PERCENT" -lt 80 ]; then
|
|
73
|
+
if [ "$COVERAGE_PERCENT" -lt "${COVERAGE_THRESHOLD:-80}" ]; then
|
|
74
74
|
# #399: a subset run (smart test selection) measures only the tested
|
|
75
75
|
# files, so its global total is meaningless as a verdict — the full
|
|
76
76
|
# suite may well be >=80% even when the subset is not. Warn exactly as
|