@boyingliu01/xp-gate 0.35.23 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapter-common.sh +78 -36
- package/adapters/csharp.sh +245 -0
- package/adapters/gate-3.sh +1 -1
- package/adapters/gate-4.sh +1 -1
- package/adapters/gate-9.sh +37 -2
- package/adapters/go.sh +6 -11
- package/adapters/iac.sh +45 -5
- package/adapters/python.sh +6 -11
- package/bin/xp-gate.js +18 -11
- package/gate-10.sh +18 -3
- package/gate-13-compiler-strictness.sh +181 -0
- package/gate-3.sh +1 -1
- package/gate-4.sh +1 -1
- package/gate-9.sh +37 -2
- package/gates/common.ts +9 -2
- package/hooks/adapter-common.sh +78 -36
- package/hooks/gate-10.sh +18 -3
- package/hooks/gate-13-compiler-strictness.sh +181 -0
- package/hooks/gate-3.sh +1 -1
- package/hooks/gate-4.sh +1 -1
- package/hooks/gate-9.sh +37 -2
- package/hooks/gate-registry.sh +161 -0
- package/hooks/lib/test-failure.sh +29 -4
- package/hooks/pre-commit +122 -3
- package/lib/__tests__/agent-runtime-journal.test.js +42 -18
- package/lib/__tests__/check-single-file.test.js +55 -4
- package/lib/__tests__/detect-deps-dsh-profile.test.js +43 -0
- package/lib/__tests__/doctor-delphi.test.js +178 -3
- package/lib/__tests__/doctor-tui-skill-freshness.test.js +3 -3
- package/lib/__tests__/install-skill-full-dir.test.js +45 -2
- package/lib/__tests__/install-skill.test.js +191 -23
- package/lib/__tests__/mirror-sync.test.js +23 -5
- package/lib/__tests__/uninstall-global-residue.test.js +394 -9
- package/lib/__tests__/update-skill-flags.test.js +78 -0
- package/lib/__tests__/update-skill.test.js +334 -57
- package/lib/detect-deps.js +24 -0
- package/lib/doctor.js +80 -12
- package/lib/install-skill.js +85 -21
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/uninstall.js +143 -12
- package/lib/update-skill.js +239 -28
- package/mock-policy/AGENTS.md +4 -4
- package/mutation/AGENTS.md +4 -4
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/dsh/lib/index.d.ts.map +1 -1
- package/plugins/dsh/lib/index.js +7 -4
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/dsh/src/index.test.ts +25 -0
- package/plugins/dsh/src/index.ts +7 -4
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +4 -4
- package/principles/AGENTS.md +4 -4
- package/principles/adapters/csharp.ts +309 -0
- package/principles/analyzer.ts +5 -0
- package/scripts/delphi-external-review.cjs +130 -101
- package/skills/delphi-review/AGENTS.md +4 -4
- package/skills/sprint-flow/AGENTS.md +4 -4
- package/skills/test-specification-alignment/AGENTS.md +4 -4
|
@@ -1180,3 +1180,184 @@ EOF
|
|
|
1180
1180
|
# stage for no benefit.
|
|
1181
1181
|
return "$GATE13_PASS"
|
|
1182
1182
|
}
|
|
1183
|
+
|
|
1184
|
+
# ============================================================================
|
|
1185
|
+
# C# / .NET PATH (#717 / REQ-005)
|
|
1186
|
+
#
|
|
1187
|
+
# Measured motivation, same shape as the C/C++ and Java paths above: `dotnet
|
|
1188
|
+
# build` on a solution with default settings reports NOTHING for a converted
|
|
1189
|
+
# nullable warning or an obsolete API, because MSBuild's default
|
|
1190
|
+
# WarningLevel/TreatWarningsAsErrors is whatever the project happens to declare.
|
|
1191
|
+
# `-warnaserror -p:Nullable=enable` turns the same source into hard errors --
|
|
1192
|
+
# which is exactly what a *compiler strictness* gate is for, and what no other
|
|
1193
|
+
# gate in the chain does (Gate 1 runs `dotnet format`, which formats and runs
|
|
1194
|
+
# analyzers but does not fail on the full compiler warning set).
|
|
1195
|
+
#
|
|
1196
|
+
# Verdict codes follow the file-level contract: 0 PASS / 1 FAIL / 2 BLOCK (tool
|
|
1197
|
+
# ran but output untrustworthy) / 3 WARN (tool absent, timeout, or warn-only).
|
|
1198
|
+
#
|
|
1199
|
+
# Deliberately driven by the adapter's own toolchain discovery rather than a
|
|
1200
|
+
# second copy of it: two independent "where is dotnet" implementations is how
|
|
1201
|
+
# detection and gates drift apart.
|
|
1202
|
+
# ============================================================================
|
|
1203
|
+
|
|
1204
|
+
# Locate the .NET SDK exactly as adapters/csharp.sh does.
|
|
1205
|
+
gate13_csharp_dotnet() {
|
|
1206
|
+
local d
|
|
1207
|
+
d="${DOTNET_BIN:-}"
|
|
1208
|
+
if [ -z "$d" ]; then
|
|
1209
|
+
d=$(command -v dotnet 2>/dev/null) || d=""
|
|
1210
|
+
fi
|
|
1211
|
+
[ -n "$d" ] || return 1
|
|
1212
|
+
"$d" --version >/dev/null 2>&1 || return 1
|
|
1213
|
+
printf '%s\n' "$d"
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
# Print the staged .cs files (not the whole build output) that Gate 13 judges.
|
|
1217
|
+
gate13_csharp_sources() {
|
|
1218
|
+
git diff --cached --name-only --diff-filter=d -- '*.cs' 2>/dev/null
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
# Print the .NET entry point to build: a solution, else a project file.
|
|
1222
|
+
#
|
|
1223
|
+
# Bare `dotnet build` in the repo root fails with MSB1003 whenever the project
|
|
1224
|
+
# lives in a subdirectory -- the normal layout (src/App/App.csproj) and the one
|
|
1225
|
+
# every fixture here uses. Measured: bare build produced no diagnostics at all
|
|
1226
|
+
# while `dotnet build P/P.csproj` produced the two real warnings. Resolving the
|
|
1227
|
+
# entry point explicitly is what makes the gate see anything.
|
|
1228
|
+
gate13_csharp_entry_point() {
|
|
1229
|
+
local f
|
|
1230
|
+
# A .sln is the widest scope and builds every project in it.
|
|
1231
|
+
f=$(git ls-files '*.sln' 2>/dev/null | head -1)
|
|
1232
|
+
if [ -n "$f" ]; then printf '%s
|
|
1233
|
+
' "$f"; return 0; fi
|
|
1234
|
+
f=$(git ls-files '*.??proj' 2>/dev/null | head -1)
|
|
1235
|
+
if [ -n "$f" ]; then printf '%s
|
|
1236
|
+
' "$f"; return 0; fi
|
|
1237
|
+
# Fall back to the working tree for a not-yet-tracked project.
|
|
1238
|
+
for f in *.sln *.??proj; do
|
|
1239
|
+
[ -f "$f" ] && { printf '%s
|
|
1240
|
+
' "$f"; return 0; }
|
|
1241
|
+
done
|
|
1242
|
+
return 1
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
# Is there a buildable .NET entry point to compile at all?
|
|
1246
|
+
gate13_csharp_has_manifest() {
|
|
1247
|
+
local f
|
|
1248
|
+
for f in *.sln *.csproj Directory.Build.props global.json; do
|
|
1249
|
+
[ -f "$f" ] && return 0
|
|
1250
|
+
done
|
|
1251
|
+
# A nested layout is normal (src/App/App.csproj).
|
|
1252
|
+
[ -n "$(git ls-files '*.*proj' '*.sln' 2>/dev/null | head -1)" ] && return 0
|
|
1253
|
+
return 1
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
# Count compiler warnings FROM CHANGED FILES only.
|
|
1257
|
+
#
|
|
1258
|
+
# `dotnet build` reports diagnostics for the whole project, so counting every
|
|
1259
|
+
# warning would make a one-line change inherit the project's entire back-catalog
|
|
1260
|
+
# and block on work the author never touched -- the false-BLOCK that Gate 13's
|
|
1261
|
+
# baseline mechanism exists to prevent. Only lines naming a file this commit
|
|
1262
|
+
# stages are counted; everything else is baseline territory.
|
|
1263
|
+
gate13_csharp_changed_warning_count() {
|
|
1264
|
+
local dotnet="$1" out entry stage_files
|
|
1265
|
+
stage_files=$(git diff --cached --name-only --diff-filter=d 2>/dev/null)
|
|
1266
|
+
[ -n "$stage_files" ] || return 1
|
|
1267
|
+
|
|
1268
|
+
# -clp:NoSummary keeps the summary table out of the parse; --nologo drops the
|
|
1269
|
+
# banner.
|
|
1270
|
+
#
|
|
1271
|
+
# --no-incremental is REQUIRED, not an optimisation tradeoff. MSBuild skips
|
|
1272
|
+
# compilation when its outputs are newer than the sources, and a skipped
|
|
1273
|
+
# compile emits no diagnostics -- measured: the same tree reported 2 warnings
|
|
1274
|
+
# from a clean build and 0 from an immediately following incremental one. A
|
|
1275
|
+
# strictness gate that only sees warnings the first time is a gate that
|
|
1276
|
+
# silently passes forever after, which is precisely the failure mode #717
|
|
1277
|
+
# exists to remove.
|
|
1278
|
+
#
|
|
1279
|
+
# The entry point is passed explicitly: bare `dotnet build` in the repo root
|
|
1280
|
+
# dies with MSB1003 whenever the project sits in a subdirectory, emitting no
|
|
1281
|
+
# diagnostics at all.
|
|
1282
|
+
entry=$(gate13_csharp_entry_point) || return 1
|
|
1283
|
+
out=$("$dotnet" build "$entry" --nologo --no-incremental -clp:NoSummary 2>&1)
|
|
1284
|
+
|
|
1285
|
+
# Report distinct CHANGED FILES, not diagnostic instances: the caller speaks in
|
|
1286
|
+
# files ("N changed C# file(s) carry compiler warnings"), and one file with two
|
|
1287
|
+
# warnings is one file to fix.
|
|
1288
|
+
#
|
|
1289
|
+
# Two normalisations are needed and both were measured, not assumed:
|
|
1290
|
+
# - dotnet prints ABSOLUTE Windows paths while git reports repo-relative
|
|
1291
|
+
# ones, so a suffix match is what relates them;
|
|
1292
|
+
# - the separators differ, so the diagnostic path is folded to "/" first.
|
|
1293
|
+
# Comparing raw strings matched nothing and reported 0 warnings on a file that
|
|
1294
|
+
# has two.
|
|
1295
|
+
printf '%s\n' "$out" \
|
|
1296
|
+
| grep -E ': (warning|error) [A-Z]+[0-9]+' \
|
|
1297
|
+
| sed -n 's/^\(.*\)(.*$/\1/p' \
|
|
1298
|
+
| tr '\\' '/' \
|
|
1299
|
+
| sort -u \
|
|
1300
|
+
| while IFS= read -r f; do
|
|
1301
|
+
[ -n "$f" ] || continue
|
|
1302
|
+
while IFS= read -r s; do
|
|
1303
|
+
[ -n "$s" ] || continue
|
|
1304
|
+
case "$f" in
|
|
1305
|
+
*"$s") printf '%s\n' "$s"; break ;;
|
|
1306
|
+
esac
|
|
1307
|
+
done <<EOF3
|
|
1308
|
+
$stage_files
|
|
1309
|
+
EOF3
|
|
1310
|
+
done \
|
|
1311
|
+
| sort -u | wc -l | tr -d ' '
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
gate13_run_csharp_analysis() {
|
|
1315
|
+
local dotnet sources tier changed_count base
|
|
1316
|
+
|
|
1317
|
+
# gate13_is_disabled takes no argument: it reads the single repo-wide
|
|
1318
|
+
# XP_GATE_DISABLE_13 switch, which disables every language path together.
|
|
1319
|
+
if gate13_is_disabled; then
|
|
1320
|
+
gate13_reason "compiler strictness disabled (XP_GATE_DISABLE_13=1)"
|
|
1321
|
+
return "$GATE13_PASS"
|
|
1322
|
+
fi
|
|
1323
|
+
|
|
1324
|
+
sources=$(gate13_csharp_sources)
|
|
1325
|
+
if [ -z "$sources" ]; then
|
|
1326
|
+
return "$GATE13_PASS"
|
|
1327
|
+
fi
|
|
1328
|
+
|
|
1329
|
+
if ! gate13_csharp_has_manifest; then
|
|
1330
|
+
# .cs files staged with no project to compile them into. Not a pass -- the
|
|
1331
|
+
# gate genuinely could not judge them -- but not a block either.
|
|
1332
|
+
gate13_reason "no .sln/.csproj found; C# sources not compiled"
|
|
1333
|
+
echo " ⏭️ SKIPPED - no .NET project to compile (.cs staged without a manifest)"
|
|
1334
|
+
return "$GATE13_WARN"
|
|
1335
|
+
fi
|
|
1336
|
+
|
|
1337
|
+
dotnet=$(gate13_csharp_dotnet) || {
|
|
1338
|
+
gate13_reason "dotnet SDK not found"
|
|
1339
|
+
echo " ⏭️ SKIPPED - dotnet SDK not installed"
|
|
1340
|
+
return "$GATE13_WARN"
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
tier=$(gate13_resolve_tier)
|
|
1344
|
+
changed_count=$(gate13_csharp_changed_warning_count "$dotnet") || changed_count=0
|
|
1345
|
+
changed_count=${changed_count:-0}
|
|
1346
|
+
|
|
1347
|
+
if [ "$changed_count" -eq 0 ]; then
|
|
1348
|
+
echo " ✅ PASSED - C# compiler strictness (no warnings in changed files)"
|
|
1349
|
+
return "$GATE13_PASS"
|
|
1350
|
+
fi
|
|
1351
|
+
|
|
1352
|
+
echo " ℹ️ ${changed_count} changed C# file(s) carry compiler warnings"
|
|
1353
|
+
|
|
1354
|
+
if [ "$tier" = "warn-only" ]; then
|
|
1355
|
+
echo " ⚠️ WARN - ${changed_count} C# file(s) with compiler warnings (warn-only tier)"
|
|
1356
|
+
return "$GATE13_WARN"
|
|
1357
|
+
fi
|
|
1358
|
+
|
|
1359
|
+
echo " ❌ BLOCKED - ${changed_count} changed C# file(s) with compiler warnings"
|
|
1360
|
+
echo " Turn the warnings into zero, or record a baseline:"
|
|
1361
|
+
echo " xp-gate compiler-baseline create"
|
|
1362
|
+
return "$GATE13_FAIL"
|
|
1363
|
+
}
|
package/hooks/gate-3.sh
CHANGED
|
@@ -363,7 +363,7 @@ else
|
|
|
363
363
|
LIZARD_PATH=$(eval echo "$LIZARD_CMD")
|
|
364
364
|
|
|
365
365
|
# Get changed source files for complexity check (includes test files)
|
|
366
|
-
CC_FILES=$(echo "$CHANGED_FILES" | grep -E '\.(ts|tsx|js|jsx|py|go|java|swift|cpp|c|hpp|h|m|mm|kt)$' || true)
|
|
366
|
+
CC_FILES=$(echo "$CHANGED_FILES" | grep -E '\.(ts|tsx|js|jsx|py|go|java|swift|cpp|c|hpp|h|m|mm|kt|cs)$' || true)
|
|
367
367
|
|
|
368
368
|
# Grade exactly the population the baseline was written from. The two must
|
|
369
369
|
# match or the ratchet reports pre-existing code as "new", which is a false
|
package/hooks/gate-4.sh
CHANGED
|
@@ -76,7 +76,7 @@ if [ "$PROJECT_LANG" = "documentation-only" ]; then
|
|
|
76
76
|
|
|
77
77
|
else
|
|
78
78
|
# Get source files to check against principles
|
|
79
|
-
PRINCIPLES_FILES=$(echo "$CHANGED_FILES" | grep -E '\.(ts|tsx|js|jsx|py|go|java|kt|dart|swift|cpp|c|hpp|h|m|mm)$' || true)
|
|
79
|
+
PRINCIPLES_FILES=$(echo "$CHANGED_FILES" | grep -E '\.(ts|tsx|js|jsx|py|go|java|kt|dart|swift|cpp|c|hpp|h|m|mm|cs)$' || true)
|
|
80
80
|
|
|
81
81
|
# One path per line, into an array. Passing $PRINCIPLES_FILES unquoted relies on
|
|
82
82
|
# word splitting to turn the list into separate argv entries, which also splits
|
package/hooks/gate-9.sh
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# ⚠️ DEAD CODE — NOT EXECUTED. Read this before changing anything here.
|
|
4
|
+
# ============================================================================
|
|
5
|
+
# This file is NOT sourced by githooks/pre-commit or githooks/pre-push (both
|
|
6
|
+
# source count = 0; verify with: grep -c "gate-9.sh" githooks/pre-commit).
|
|
7
|
+
# It is shipped by githooks/install.sh but never runs.
|
|
8
|
+
#
|
|
9
|
+
# The gate that actually runs semgrep SAST is githooks/gate-10.sh, and it
|
|
10
|
+
# reports as `gate10_sast`. Gate 9 in the report is `gate9_build_integrity`
|
|
11
|
+
# (Build Integrity), a different gate entirely.
|
|
12
|
+
#
|
|
13
|
+
# This mislabelling is not cosmetic: in #717 a C# SAST fix was first written
|
|
14
|
+
# into THIS file, where it changed nothing, while .cs stayed ungoverned. The
|
|
15
|
+
# C# ruleset loading that works lives in gate-10.sh. Do not relocate it here.
|
|
16
|
+
#
|
|
17
|
+
# Kept rather than deleted because githooks/install.sh:GATE_SCRIPTS ships it;
|
|
18
|
+
# removing it is a separate change. See issue #717 walkthrough MC-03.
|
|
19
|
+
# ============================================================================
|
|
1
20
|
# ============================================================================
|
|
2
21
|
# GATE 9: Semgrep SAST Security Scan
|
|
3
22
|
# Detects security vulnerabilities (SQL injection, XSS, etc.) in staged files
|
|
@@ -112,8 +131,22 @@ EOF
|
|
|
112
131
|
# code is demoted to "did the tool itself run". --error stays OFF: it would
|
|
113
132
|
# add nothing the JSON does not already provide, and keeping it off leaves
|
|
114
133
|
# failure exit codes unambiguous for the #475 diagnostics path.
|
|
134
|
+
# #717: p/security-audit carries NO C# rules. Measured: a .cs file with a
|
|
135
|
+
# textbook SQL injection (string-concatenated SqlCommand) and a
|
|
136
|
+
# Process.Start command injection produced 0 findings under
|
|
137
|
+
# p/security-audit and 1 ERROR under p/csharp. Since gate-9.sh:45 lists
|
|
138
|
+
# `cs` in the file filter, .cs was accepted and then scanned by a ruleset
|
|
139
|
+
# that cannot see C# -- the identical "filter looks like coverage" defect
|
|
140
|
+
# this issue removed from Gate 4. p/csharp is loaded IN ADDITION to
|
|
141
|
+
# p/security-audit (not instead of: the security-audit rules still apply
|
|
142
|
+
# to the other languages in a mixed commit).
|
|
143
|
+
SEMGREP_RULES=(--config=p/security-audit)
|
|
144
|
+
if printf "%s
|
|
145
|
+
" "$SEMGREP_FILES" | grep -qE ".cs$"; then
|
|
146
|
+
SEMGREP_RULES+=(--config=p/csharp)
|
|
147
|
+
fi
|
|
115
148
|
SEMGREP_STDOUT_FILE=$(mktemp)
|
|
116
|
-
SEMGREP_OUTPUT=$("$SEMGREP_CMD" scan
|
|
149
|
+
SEMGREP_OUTPUT=$("$SEMGREP_CMD" scan "${SEMGREP_RULES[@]}" --json --disable-version-check "${SEMGREP_ARGS[@]}" 2>&1 >"$SEMGREP_STDOUT_FILE")
|
|
117
150
|
SEMGREP_EXIT=$?
|
|
118
151
|
|
|
119
152
|
# One node pass over the JSON: first line = verdict counts, remaining lines =
|
|
@@ -191,7 +224,9 @@ console.log("");});
|
|
|
191
224
|
echo " ❌ BLOCKED — blocking-severity vulnerability found"
|
|
192
225
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
193
226
|
echo "$FINDING_DETAILS"
|
|
194
|
-
|
|
227
|
+
# #717: name BOTH rulesets. A C# finding comes from p/csharp, so
|
|
228
|
+
# pointing the author at p/security-audit alone reproduces zero hits.
|
|
229
|
+
echo " Run 'semgrep scan --config=p/security-audit --config=p/csharp' to review all findings."
|
|
195
230
|
GATE_9_STATUS="FAIL"
|
|
196
231
|
exit 1
|
|
197
232
|
else
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# GATE REGISTRY — the single source of truth for "which gate is which"
|
|
4
|
+
# ============================================================================
|
|
5
|
+
#
|
|
6
|
+
# WHY THIS FILE EXISTS
|
|
7
|
+
#
|
|
8
|
+
# Gate numbers alone are not self-describing, and the numbers are not stable
|
|
9
|
+
# facts about the repository — they are labels. Three sources disagreed about
|
|
10
|
+
# what "Gate 9" is:
|
|
11
|
+
#
|
|
12
|
+
# root AGENTS.md -> Semgrep SAST (WRONG)
|
|
13
|
+
# githooks/AGENTS.md -> Build Integrity (right)
|
|
14
|
+
# pre-commit actual -> Build Integrity (right)
|
|
15
|
+
#
|
|
16
|
+
# In issue #717 a C# SAST fix was written into githooks/gate-9.sh, because the
|
|
17
|
+
# root document said Gate 9 was SAST. SAST is gate 10. gate-9.sh is not sourced
|
|
18
|
+
# by any hook and never executes, so the fix changed nothing while appearing to
|
|
19
|
+
# land. The defect was not "the number was hard to remember" — it was that
|
|
20
|
+
# nothing connected a gate's NAME to the FILE that runs it.
|
|
21
|
+
#
|
|
22
|
+
# This registry makes that connection explicit and testable. The drift test
|
|
23
|
+
# (githooks/__tests__/gate-registry.test.bats) asserts every `impl` below
|
|
24
|
+
# against the code, so a document that disagrees with reality, or a fix that
|
|
25
|
+
# lands in a file nobody sources, fails immediately.
|
|
26
|
+
#
|
|
27
|
+
# COLUMNS
|
|
28
|
+
# name stable semantic id — use this in prose, commits, and tests
|
|
29
|
+
# number display label only. Mirrors the report key (gateN_*) for backward
|
|
30
|
+
# compatibility; do NOT treat it as the identity of the gate.
|
|
31
|
+
# impl where the gate actually runs. One of:
|
|
32
|
+
# <script>.sh a file sourced/executed by pre-commit
|
|
33
|
+
# inline implemented inside pre-commit itself
|
|
34
|
+
# blocks YES the gate can reject a commit
|
|
35
|
+
# NO the gate reports but cannot reject
|
|
36
|
+
#
|
|
37
|
+
# USAGE
|
|
38
|
+
# source githooks/gate-registry.sh
|
|
39
|
+
# gate_name_for_file gate-10.sh # -> sast
|
|
40
|
+
# gate_impl sast # -> gate-10.sh
|
|
41
|
+
# gate_number sast # -> 10
|
|
42
|
+
# gate_can_block sast # -> YES
|
|
43
|
+
# gate_registry_dump # -> one line per gate
|
|
44
|
+
#
|
|
45
|
+
# ⚠️ Adding a gate: add a row here AND update the drift test's expectations.
|
|
46
|
+
# A gate that exists only in pre-commit and not here is a gate nobody can
|
|
47
|
+
# look up by name — which is how #717's wrong-file patch happened.
|
|
48
|
+
# ============================================================================
|
|
49
|
+
|
|
50
|
+
# name|number|impl|blocks
|
|
51
|
+
GATE_REGISTRY_ROWS=(
|
|
52
|
+
"version-consistency|0|inline|YES"
|
|
53
|
+
"code-quality|1|inline|NO"
|
|
54
|
+
"duplicate-code|2|inline|NO"
|
|
55
|
+
"complexity|3|gate-3.sh|YES"
|
|
56
|
+
"principles|4|gate-4.sh|YES"
|
|
57
|
+
"tests-coverage|5|inline|YES"
|
|
58
|
+
"architecture|6|inline|YES"
|
|
59
|
+
"iac-security|7|gate-7.sh|YES"
|
|
60
|
+
"secret-scanning|8|gate-8.sh|YES"
|
|
61
|
+
"build-integrity|9|inline|NO"
|
|
62
|
+
"sast|10|gate-10.sh|YES"
|
|
63
|
+
"sprint-flow|11|sprint-gate.sh|YES"
|
|
64
|
+
"file-hygiene|12|gate-12-file-hygiene.sh|NO"
|
|
65
|
+
"compiler-strictness|13|gate-13-compiler-strictness.sh|YES"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Human-readable title for each name. Kept separate from the row above so the
|
|
69
|
+
# title can change without touching the lookup keys.
|
|
70
|
+
_gate_title() {
|
|
71
|
+
case "$1" in
|
|
72
|
+
version-consistency) echo "Version Consistency" ;;
|
|
73
|
+
code-quality) echo "Code Quality (Static + Lint)" ;;
|
|
74
|
+
duplicate-code) echo "Duplicate Code" ;;
|
|
75
|
+
complexity) echo "Cyclomatic Complexity" ;;
|
|
76
|
+
principles) echo "Clean Code + SOLID" ;;
|
|
77
|
+
tests-coverage) echo "Tests + Coverage" ;;
|
|
78
|
+
architecture) echo "Architecture + Boy Scout Rule" ;;
|
|
79
|
+
iac-security) echo "IaC Security Scanning" ;;
|
|
80
|
+
secret-scanning) echo "Secret Scanning" ;;
|
|
81
|
+
build-integrity) echo "Build Integrity" ;;
|
|
82
|
+
sast) echo "SAST Security Scan" ;;
|
|
83
|
+
sprint-flow) echo "Sprint Flow Enforcement" ;;
|
|
84
|
+
file-hygiene) echo "File Hygiene Check" ;;
|
|
85
|
+
compiler-strictness) echo "Compiler Strictness" ;;
|
|
86
|
+
*) return 1 ;;
|
|
87
|
+
esac
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
# Lookups. Each returns non-zero when the name is unknown, so a typo fails
|
|
92
|
+
# loudly instead of silently matching nothing.
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
# gate_row <name> -> echoes the raw row, or returns 1.
|
|
96
|
+
gate_row() {
|
|
97
|
+
local want="$1" row
|
|
98
|
+
for row in "${GATE_REGISTRY_ROWS[@]}"; do
|
|
99
|
+
case "$row" in
|
|
100
|
+
"$want"\|*) printf '%s\n' "$row"; return 0 ;;
|
|
101
|
+
esac
|
|
102
|
+
done
|
|
103
|
+
return 1
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# _gate_field <name> <1-based field index>
|
|
107
|
+
_gate_field() {
|
|
108
|
+
local row
|
|
109
|
+
row=$(gate_row "$1") || return 1
|
|
110
|
+
printf '%s\n' "$row" | cut -d'|' -f"$2"
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
gate_number() { _gate_field "$1" 2; }
|
|
114
|
+
gate_impl() { _gate_field "$1" 3; }
|
|
115
|
+
gate_blocks() { _gate_field "$1" 4; }
|
|
116
|
+
gate_can_block() { [ "$(gate_blocks "$1")" = "YES" ] && echo YES || echo NO; }
|
|
117
|
+
gate_title() { _gate_title "$1"; }
|
|
118
|
+
|
|
119
|
+
# gate_name_for_number <N> -> echoes the semantic name.
|
|
120
|
+
gate_name_for_number() {
|
|
121
|
+
local want="$1" row
|
|
122
|
+
for row in "${GATE_REGISTRY_ROWS[@]}"; do
|
|
123
|
+
case "$row" in
|
|
124
|
+
*\|"$want"\|*) printf '%s\n' "${row%%|*}"; return 0 ;;
|
|
125
|
+
esac
|
|
126
|
+
done
|
|
127
|
+
return 1
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
# gate_name_for_file <file> -> semantic name of the gate implemented by <file>.
|
|
131
|
+
# Returns 1 for `inline` gates and unknown files. This is the lookup that would
|
|
132
|
+
# have caught #717: gate-9.sh resolves to build-integrity, not sast.
|
|
133
|
+
gate_name_for_file() {
|
|
134
|
+
local want="$1" row impl name
|
|
135
|
+
for row in "${GATE_REGISTRY_ROWS[@]}"; do
|
|
136
|
+
name="${row%%|*}"
|
|
137
|
+
impl=$(printf '%s\n' "$row" | cut -d'|' -f3)
|
|
138
|
+
[ "$impl" = "$want" ] && { printf '%s\n' "$name"; return 0; }
|
|
139
|
+
done
|
|
140
|
+
return 1
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# List every script the registry claims the hook runs.
|
|
144
|
+
gate_all_impls() {
|
|
145
|
+
local row impl
|
|
146
|
+
for row in "${GATE_REGISTRY_ROWS[@]}"; do
|
|
147
|
+
impl=$(printf '%s\n' "$row" | cut -d'|' -f3)
|
|
148
|
+
[ "$impl" != "inline" ] && printf '%s\n' "$impl"
|
|
149
|
+
done
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
gate_registry_dump() {
|
|
153
|
+
local row name num impl blocks
|
|
154
|
+
for row in "${GATE_REGISTRY_ROWS[@]}"; do
|
|
155
|
+
name="${row%%|*}"
|
|
156
|
+
num=$(printf '%s\n' "$row" | cut -d'|' -f2)
|
|
157
|
+
impl=$(printf '%s\n' "$row" | cut -d'|' -f3)
|
|
158
|
+
blocks=$(printf '%s\n' "$row" | cut -d'|' -f4)
|
|
159
|
+
printf '%-3s %-22s %-34s %-6s %s\n' "$num" "$name" "$(_gate_title "$name")" "$blocks" "$impl"
|
|
160
|
+
done
|
|
161
|
+
}
|
|
@@ -31,6 +31,20 @@ has_real_failure_marker() {
|
|
|
31
31
|
is_runner_infrastructure_error() {
|
|
32
32
|
local _out="$1"
|
|
33
33
|
[ -n "$_out" ] || return 1
|
|
34
|
+
# #717: dotnet/VSTest's testhost failing to START is the same class of event
|
|
35
|
+
# -- the runner could not run, so no test was judged. Its signature is a
|
|
36
|
+
# Win32Exception from ProcessManager.OpenProcess (the testhost cannot open a
|
|
37
|
+
# handle to its own parent) followed by "Testhost process exited with error".
|
|
38
|
+
# Without this arm, a .NET repo on a machine with that permission fault would
|
|
39
|
+
# report "Tests FAILED" on every commit for a reason that has nothing to do
|
|
40
|
+
# with its tests.
|
|
41
|
+
#
|
|
42
|
+
# A real dotnet failure ("Failed!", "Failed: 3") never carries these strings,
|
|
43
|
+
# so the has_real_failure_marker guard below still lets genuine failures block.
|
|
44
|
+
if printf '%s' "$_out" | grep -qE 'Win32Exception|Testhost.*(exited with error|已退出)|test host.*(could not|did not) start'; then
|
|
45
|
+
has_real_failure_marker "$_out" && return 1
|
|
46
|
+
return 0
|
|
47
|
+
fi
|
|
34
48
|
printf '%s' "$_out" | grep -qE 'Unhandled Error' || return 1
|
|
35
49
|
has_real_failure_marker "$_out" && return 1
|
|
36
50
|
return 0
|
|
@@ -116,10 +130,21 @@ handle_test_failure() {
|
|
|
116
130
|
local _ctx="$2"
|
|
117
131
|
if is_runner_infrastructure_error "$_out"; then
|
|
118
132
|
echo ""
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
133
|
+
# The advice has to match the runner that failed (#717). A C# developer
|
|
134
|
+
# handed the vitest EPERM workaround would chase a cache problem in a
|
|
135
|
+
# language they are not using, and the real cause -- the testhost could not
|
|
136
|
+
# open a handle to its own parent -- would never be named.
|
|
137
|
+
if printf '%s' "$_out" | grep -qE 'Win32Exception|Testhost.*(exited with error|已退出)|test host.*(could not|did not) start'; then
|
|
138
|
+
echo "⏭️ SKIPPED - dotnet test host could not start (no test was judged)"
|
|
139
|
+
echo " Detected: VSTest testhost exited before running a test."
|
|
140
|
+
echo " This is an environment fault, NOT a test verdict -- it must not block."
|
|
141
|
+
echo " Check: antivirus / endpoint protection blocking process handles, or run the suite outside this path."
|
|
142
|
+
else
|
|
143
|
+
echo "⏭️ SKIPPED - vitest runner infrastructure error (no test failed)"
|
|
144
|
+
echo " Detected: Unhandled Errors with 0 failed tests."
|
|
145
|
+
echo " Known Windows issue: EPERM on vitest temp/ssr cache (#454)."
|
|
146
|
+
echo " Workaround: npx vitest run --no-file-parallelism <targets>"
|
|
147
|
+
fi
|
|
123
148
|
TESTS_SKIPPED=true
|
|
124
149
|
TESTS_EXIT_CODE=0
|
|
125
150
|
return 0
|
package/hooks/pre-commit
CHANGED
|
@@ -271,6 +271,17 @@ if [ -z "$GATE_DIR" ]; then
|
|
|
271
271
|
GATE_DIR="$ADAPTER_DIR"
|
|
272
272
|
fi
|
|
273
273
|
|
|
274
|
+
# Gate registry (#717 walkthrough): binds each gate's NAME to the file that
|
|
275
|
+
# runs it, so a gate can be referred to by what it does rather than by a
|
|
276
|
+
# positional number. Optional by design — a consumer repo installed from an
|
|
277
|
+
# older package will not have it, and the hooks must keep working there.
|
|
278
|
+
_GATE_REGISTRY="$GATE_DIR/gate-registry.sh"
|
|
279
|
+
[ -f "$_GATE_REGISTRY" ] || _GATE_REGISTRY="$(git rev-parse --show-toplevel 2>/dev/null)/githooks/gate-registry.sh"
|
|
280
|
+
if [ -f "$_GATE_REGISTRY" ]; then
|
|
281
|
+
# shellcheck source=/dev/null
|
|
282
|
+
. "$_GATE_REGISTRY"
|
|
283
|
+
fi
|
|
284
|
+
|
|
274
285
|
# Trap: ensure quality report generated on ANY exit (pass or fail).
|
|
275
286
|
# Using a guard prevents recursion when 'exit' is called from inside the trap.
|
|
276
287
|
# IMPORTANT: Captures exit status FIRST so generate_quality_report cannot override it.
|
|
@@ -1267,6 +1278,14 @@ if [ -z "$PROJECT_LANGS" ] && [ -z "$_CONFIG_LANGS" ]; then
|
|
|
1267
1278
|
if [ "$(_detect_ext_lang "ps1" "psm1")" = "1" ]; then
|
|
1268
1279
|
PROJECT_LANGS="$PROJECT_LANGS powershell"
|
|
1269
1280
|
fi
|
|
1281
|
+
# #717: without this arm a staged .cs file never produced a `csharp` entry in
|
|
1282
|
+
# PROJECT_LANGS, so the per-language loop at the adapter-routing site had no
|
|
1283
|
+
# turn in which to resolve adapters/csharp.sh -- the adapter was unreachable
|
|
1284
|
+
# no matter how correct it was. Detection on extensions (not manifests) is why
|
|
1285
|
+
# one arm is enough: a repo staging .cs always lands here.
|
|
1286
|
+
if [ "$(_detect_ext_lang "cs" "csx")" = "1" ]; then
|
|
1287
|
+
PROJECT_LANGS="$PROJECT_LANGS csharp"
|
|
1288
|
+
fi
|
|
1270
1289
|
# IaC: .tf, .tfvars, Dockerfile, docker-compose*, .yaml/.yml in kube context
|
|
1271
1290
|
if echo "$CHANGED_FILES" | grep -qE '(\.tf$|\.tfvars$|Dockerfile|docker-compose)' 2>/dev/null; then
|
|
1272
1291
|
PROJECT_LANGS="$PROJECT_LANGS iac"
|
|
@@ -2256,7 +2275,18 @@ else
|
|
|
2256
2275
|
if [ "$LINT_EXIT" -ne 0 ]; then
|
|
2257
2276
|
echo "ℹ️ Lint check had issues (may be non-blocking)"
|
|
2258
2277
|
fi
|
|
2259
|
-
|
|
2278
|
+
|
|
2279
|
+
# #717: rc=2 is the adapters' "could not execute" channel (no toolchain,
|
|
2280
|
+
# or a tool that refused to start), distinct from rc=1 "ran and found
|
|
2281
|
+
# problems". Collapsing both into one SKIPPED line -- as this branch did
|
|
2282
|
+
# -- makes a permanently-broken analyzer indistinguishable from a clean
|
|
2283
|
+
# one, which is the silent-failure shape #717 is about. The gate still
|
|
2284
|
+
# does not block (tool-missing must not block), but the reason is now
|
|
2285
|
+
# named in the log instead of implied.
|
|
2286
|
+
if [ "$ANALYSIS_EXIT" -eq 2 ] || [ "$LINT_EXIT" -eq 2 ]; then
|
|
2287
|
+
echo "⏭️ SKIPPED - Code quality (${CURRENT_LANG} toolchain could not execute — NOT a pass)"
|
|
2288
|
+
fi
|
|
2289
|
+
|
|
2260
2290
|
if [ "$ANALYSIS_EXIT" -eq 0 ] && [ "$LINT_EXIT" -eq 0 ]; then
|
|
2261
2291
|
echo "✅ PASSED - Language-specific code quality checks."
|
|
2262
2292
|
else
|
|
@@ -4307,8 +4337,72 @@ JAVAARCHROOTS
|
|
|
4307
4337
|
fi
|
|
4308
4338
|
fi
|
|
4309
4339
|
;;
|
|
4340
|
+
"csharp")
|
|
4341
|
+
# #717: C# had no arm at all, so it fell through to the `*)` default
|
|
4342
|
+
# below and was reported as "not configured for csharp" -- a language
|
|
4343
|
+
# the gate silently could not measure, with no way to configure it
|
|
4344
|
+
# either, because the config file was never looked for.
|
|
4345
|
+
#
|
|
4346
|
+
# Architecture for .NET is normally expressed as *tests* (NetArchTest,
|
|
4347
|
+
# ArchUnitNET, or a plain xUnit project asserting layering). There is no
|
|
4348
|
+
# universal CLI, so this arm keys off a config the project opts into,
|
|
4349
|
+
# exactly like the Go arm's arch-go.yaml.
|
|
4350
|
+
if [ -f "arch-csharp.yaml" ] || [ -f ".xp-gate/arch-csharp.yaml" ]; then
|
|
4351
|
+
_cs_arch_cfg="arch-csharp.yaml"
|
|
4352
|
+
[ -f "$_cs_arch_cfg" ] || _cs_arch_cfg=".xp-gate/arch-csharp.yaml"
|
|
4353
|
+
# Find an architecture test project: a .csproj whose name or contents
|
|
4354
|
+
# mention architecture tests. Guessing one fixed path is the #602
|
|
4355
|
+
# defect, which reported "not configured" for repos that had them.
|
|
4356
|
+
_cs_arch_proj=""
|
|
4357
|
+
while IFS= read -r _p; do
|
|
4358
|
+
[ -n "$_p" ] || continue
|
|
4359
|
+
if grep -qiE "architecture|archunit|netarchtest" "$_p" 2>/dev/null; then
|
|
4360
|
+
_cs_arch_proj="$_p"
|
|
4361
|
+
break
|
|
4362
|
+
fi
|
|
4363
|
+
done <<CSARCHPROJ
|
|
4364
|
+
$(git ls-files '*.csproj' 2>/dev/null)
|
|
4365
|
+
CSARCHPROJ
|
|
4366
|
+
|
|
4367
|
+
if [ -z "$_cs_arch_proj" ]; then
|
|
4368
|
+
echo " ℹ️ No architecture test project found - skipping C# architecture"
|
|
4369
|
+
echo " ⏭️ SKIPPED - C# architecture validation (no arch test project)"
|
|
4370
|
+
if [ "${BASH_VERSINFO[0]:-0}" -ge 4 ]; then
|
|
4371
|
+
GATE_6_REASON_BY_LANG["$CURRENT_LANG"]="architecture_not_configured"
|
|
4372
|
+
fi
|
|
4373
|
+
elif ! command -v dotnet >/dev/null 2>&1; then
|
|
4374
|
+
echo " ⏭️ SKIPPED - C# architecture validation (dotnet SDK not installed)"
|
|
4375
|
+
if [ "${BASH_VERSINFO[0]:-0}" -ge 4 ]; then
|
|
4376
|
+
GATE_6_REASON_BY_LANG["$CURRENT_LANG"]="tool_missing"
|
|
4377
|
+
fi
|
|
4378
|
+
else
|
|
4379
|
+
echo " Running C# architecture tests ($_cs_arch_proj)..."
|
|
4380
|
+
# Same reading as the Go/Java arms (#630/#602): the checker's own
|
|
4381
|
+
# status decides the verdict. Piping into `tail` and printing
|
|
4382
|
+
# "completed" unconditionally is the fail-open shape those issues
|
|
4383
|
+
# removed; PIPESTATUS[0] is what keeps this arm from repeating it.
|
|
4384
|
+
dotnet test "$_cs_arch_proj" --nologo 2>&1 | tail -20
|
|
4385
|
+
_cs_arch_rc="${PIPESTATUS[0]}"
|
|
4386
|
+
if [ "$_cs_arch_rc" -ne 0 ]; then
|
|
4387
|
+
echo ""
|
|
4388
|
+
echo "❌ BLOCKED - C# architecture violations ($_cs_arch_proj, exit $_cs_arch_rc)"
|
|
4389
|
+
echo "Fix the layering violation(s) above before committing."
|
|
4390
|
+
if [ "${BASH_VERSINFO[0]:-0}" -ge 4 ]; then
|
|
4391
|
+
GATE_6_REASON_BY_LANG["$CURRENT_LANG"]="architecture_tests_failed"
|
|
4392
|
+
fi
|
|
4393
|
+
exit 1
|
|
4394
|
+
fi
|
|
4395
|
+
echo " ✅ C# architecture validation completed."
|
|
4396
|
+
fi
|
|
4397
|
+
else
|
|
4398
|
+
echo " ℹ️ No arch-csharp.yaml found - skipping C# architecture"
|
|
4399
|
+
echo " ⏭️ SKIPPED - C# architecture validation (no config)"
|
|
4400
|
+
if [ "${BASH_VERSINFO[0]:-0}" -ge 4 ]; then
|
|
4401
|
+
GATE_6_REASON_BY_LANG["$CURRENT_LANG"]="architecture_not_configured"
|
|
4402
|
+
fi
|
|
4403
|
+
fi
|
|
4404
|
+
;;
|
|
4310
4405
|
*)
|
|
4311
|
-
echo " ℹ️ Architecture validation not configured for $CURRENT_LANG"
|
|
4312
4406
|
echo " ⏭️ SKIPPED - Architecture validation (not configured for $CURRENT_LANG)"
|
|
4313
4407
|
# #537: archlint ships no detector for this language. Say so in a way
|
|
4314
4408
|
# the quality report can read, instead of leaving the dimension silent.
|
|
@@ -4987,6 +5081,19 @@ if [ -n "$MIRROR_STAGED" ] && printf '%s\n' "$MIRROR_STAGED" | grep -qE '^(githo
|
|
|
4987
5081
|
# each other, and the only way past was to edit a generated file by hand.
|
|
4988
5082
|
case "$_mf" in
|
|
4989
5083
|
*/__tests__/*) continue ;;
|
|
5084
|
+
# AGENTS.md is documentation, not a hook artifact: sync-package-content.js
|
|
5085
|
+
# copies the hook suite, and no AGENTS.md exists under hooks/. Demanding a
|
|
5086
|
+
# mirror for it reported a BLOCK on every doc edit (harmless only because
|
|
5087
|
+
# GATE_FAILED was never read -- see below).
|
|
5088
|
+
*/AGENTS.md) continue ;;
|
|
5089
|
+
# `githooks/adapters/` mirrors to `src/npm-package/adapters/`, NOT under
|
|
5090
|
+
# `hooks/` -- that is what scripts/check-hook-mirror.sh declares (line 6)
|
|
5091
|
+
# and what sync-package-content.js writes. The blanket `githooks/*` rule
|
|
5092
|
+
# below sent it to `src/npm-package/hooks/adapters/<f>`, a path that has
|
|
5093
|
+
# never existed, so ANY edit to an adapter was un-committable with
|
|
5094
|
+
# "has no mirror at". It stayed hidden because nobody had edited an
|
|
5095
|
+
# adapter since this gate was narrowed to diff files.
|
|
5096
|
+
githooks/adapters/*) _mm="src/npm-package/adapters/${_mf#githooks/adapters/}" ;;
|
|
4990
5097
|
githooks/*) _mm="src/npm-package/hooks/${_mf#githooks/}" ;;
|
|
4991
5098
|
plugins/*/src/*) _mm=$(printf '%s' "$_mf" | sed 's|^plugins/\([^/]*\)/src/|src/npm-package/plugins/\1/src/|') ;;
|
|
4992
5099
|
*) continue ;;
|
|
@@ -5006,7 +5113,15 @@ if [ -n "$MIRROR_STAGED" ] && printf '%s\n' "$MIRROR_STAGED" | grep -qE '^(githo
|
|
|
5006
5113
|
echo "❌ BLOCKED - Gate 12b: $MIRROR_FAIL mirror parity violation(s)."
|
|
5007
5114
|
echo " The mirror is what gets published; a mirror-only fix is silently"
|
|
5008
5115
|
echo " reverted by the next prepack (v0.27.3 shipped that bug)."
|
|
5009
|
-
GATE_FAILED=1
|
|
5116
|
+
# BLOCKS, and must actually block. This used to set GATE_FAILED=1, a
|
|
5117
|
+
# variable that appears exactly once in this file -- the assignment --
|
|
5118
|
+
# and is read nowhere, so the gate printed BLOCKED on every doc edit and
|
|
5119
|
+
# let the commit through. A gate that reports a verdict nobody consumes is
|
|
5120
|
+
# the same failure as a filter that accepts a file no adapter claims:
|
|
5121
|
+
# it looks like enforcement. exit is the contract every other blocking
|
|
5122
|
+
# gate here uses (see Gate 13: `exit "$_g13_worst"`).
|
|
5123
|
+
GATE_12_STATUS=WARN
|
|
5124
|
+
exit 1
|
|
5010
5125
|
else
|
|
5011
5126
|
echo "✅ PASSED - Gate 12b: mirror parity (touched files)"
|
|
5012
5127
|
fi
|
|
@@ -5061,6 +5176,10 @@ elif [ -f "$GATE_DIR/gate-13-compiler-strictness.sh" ] || [ -f "$(git rev-parse
|
|
|
5061
5176
|
|
|
5062
5177
|
gate13_run_cpp_analysis; _g13_rc=$?; _g13_note "$_g13_rc"
|
|
5063
5178
|
gate13_run_java_analysis; _g13_rc=$?; _g13_note "$_g13_rc"
|
|
5179
|
+
# #717: C# joins the same reduce-the-worst loop. Without this call the
|
|
5180
|
+
# function would exist but never execute -- the "silently skipped" shape
|
|
5181
|
+
# #717 is about, one level up.
|
|
5182
|
+
gate13_run_csharp_analysis; _g13_rc=$?; _g13_note "$_g13_rc"
|
|
5064
5183
|
gate13_run_analyzer; _g13_rc=$?; _g13_note "$_g13_rc"
|
|
5065
5184
|
|
|
5066
5185
|
exit "$_g13_worst"
|