@boyingliu01/xp-gate 0.35.24 → 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 +55 -2
- 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/iac.sh +45 -5
- 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 +55 -2
- 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 +175 -0
- package/lib/__tests__/install-skill-full-dir.test.js +45 -2
- package/lib/__tests__/install-skill.test.js +84 -3
- package/lib/__tests__/mirror-sync.test.js +23 -5
- package/lib/__tests__/uninstall-global-residue.test.js +192 -28
- package/lib/__tests__/update-skill-flags.test.js +78 -0
- package/lib/__tests__/update-skill.test.js +156 -3
- package/lib/detect-deps.js +24 -0
- package/lib/doctor.js +80 -12
- package/lib/install-skill.js +67 -18
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/uninstall.js +96 -2
- package/lib/update-skill.js +143 -2
- 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/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/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/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
|
package/gates/common.ts
CHANGED
|
@@ -183,8 +183,15 @@ export function getTempDir(): string {
|
|
|
183
183
|
|
|
184
184
|
// ── File Filtering Helpers ──
|
|
185
185
|
|
|
186
|
-
/**
|
|
187
|
-
|
|
186
|
+
/**
|
|
187
|
+
* Source file extensions that lizard can analyze.
|
|
188
|
+
*
|
|
189
|
+
* `cs` belongs here because lizard parses C# natively (#717). Omitting it was
|
|
190
|
+
* never a tool limitation: lizard reports the CCN 11-22 functions the issue
|
|
191
|
+
* names, and the gate dropped those files before lizard was invoked, so the
|
|
192
|
+
* report said "no source files to check" for a tree full of them.
|
|
193
|
+
*/
|
|
194
|
+
const LIZARD_EXTENSIONS = /\.(ts|tsx|js|jsx|py|go|java|swift|cpp|c|hpp|h|m|mm|kt|cs)$/;
|
|
188
195
|
|
|
189
196
|
/** File extensions that semgrep supports. */
|
|
190
197
|
const SEMGREP_EXTENSIONS = /\.(ts|tsx|js|jsx|py|go|java|c|cpp|cs|rb|php|scala|swift)$/;
|
package/hooks/adapter-common.sh
CHANGED
|
@@ -38,8 +38,46 @@ run_without_git_context() (
|
|
|
38
38
|
"$@"
|
|
39
39
|
)
|
|
40
40
|
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# C# / .NET manifests (#717)
|
|
43
|
+
#
|
|
44
|
+
# A .NET tree can declare itself three ways: a project file (*.csproj), a
|
|
45
|
+
# solution (*.sln), or a Directory.Build.props that carries the real settings
|
|
46
|
+
# for a whole directory subtree with no project file of its own. All three are
|
|
47
|
+
# checked, because a repo may carry only the latter two.
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
# Succeeds when the current directory holds a .NET manifest at depth 1.
|
|
51
|
+
has_dotnet_manifest() {
|
|
52
|
+
[ -f "global.json" ] && return 0
|
|
53
|
+
[ -f "Directory.Build.props" ] && return 0
|
|
54
|
+
[ -f "Directory.Build.targets" ] && return 0
|
|
55
|
+
# `find -maxdepth 1` rather than a glob loop: a repo with thousands of files
|
|
56
|
+
# in one directory would expand a `*.csproj` glob into an unusable argv.
|
|
57
|
+
# -not -path "./.*" keeps a stray file under .git/.vscode from declaring the
|
|
58
|
+
# project language.
|
|
59
|
+
[ -n "$(find . -maxdepth 1 -type f \( -name '*.csproj' -o -name '*.sln' \) 2>/dev/null | sed -n '1p; 1q')" ]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Succeeds when a .cs source exists below the current directory.
|
|
63
|
+
has_csharp_sources() {
|
|
64
|
+
[ -n "$(find . -name "*.cs" -type f -not -path "./.git/*" -not -path "*/node_modules/*" -not -path "*/bin/*" -not -path "*/obj/*" 2>/dev/null | sed -n '1p; 1q')" ]
|
|
65
|
+
}
|
|
66
|
+
|
|
41
67
|
detect_project_lang() {
|
|
42
|
-
|
|
68
|
+
# #717: the C# arms come FIRST, before tsconfig.json / package.json /
|
|
69
|
+
# .sh sniffing. Detection below is an exclusive first-match chain, and a .NET
|
|
70
|
+
# repo routinely carries a JS or shell half (the dsh-aot-runner shape in the
|
|
71
|
+
# issue is 2031 lines of C# plus ~900 lines of JS). Ordered after the
|
|
72
|
+
# TypeScript arm, that repo reported "typescript" and every C# file was
|
|
73
|
+
# invisible to language-routed gates -- the silent skip #717 is about.
|
|
74
|
+
#
|
|
75
|
+
# Placing C# first cannot steal an existing project: none of the languages
|
|
76
|
+
# matched afterwards ever ship a .csproj/.sln/Directory.Build.props, so the
|
|
77
|
+
# pre-existing arms are reached unchanged whenever those files are absent.
|
|
78
|
+
if has_dotnet_manifest; then
|
|
79
|
+
echo "csharp"
|
|
80
|
+
elif [ -f "tsconfig.json" ]; then
|
|
43
81
|
echo "typescript"
|
|
44
82
|
elif [ -f "pyproject.toml" ] || [ -f "requirements.txt" ] || [ -f "setup.py" ]; then
|
|
45
83
|
echo "python"
|
|
@@ -88,6 +126,8 @@ detect_project_lang() {
|
|
|
88
126
|
else
|
|
89
127
|
echo "dart"
|
|
90
128
|
fi
|
|
129
|
+
elif [ -n "$(find . -name "*.cs" -type f -not -path "./.git/*" -not -path "*/bin/*" -not -path "*/obj/*" | sed -n '1p; 1q')" ]; then
|
|
130
|
+
echo "csharp"
|
|
91
131
|
elif [ -n "$(find . -name "*.swift" -type f | sed -n '1p; 1q')" ]; then
|
|
92
132
|
echo "swift"
|
|
93
133
|
elif [ -n "$(find . -name "*.cpp" -o -name "*.cc" -o -name "*.c" -o -name "*.h" -type f | sed -n '1p; 1q')" ]; then
|
|
@@ -104,6 +144,18 @@ detect_project_lang() {
|
|
|
104
144
|
fi
|
|
105
145
|
}
|
|
106
146
|
|
|
147
|
+
# ---------------------------------------------------------------------------
|
|
148
|
+
# NOTE (#717): a `detect_project_langs()` collector was added here and then
|
|
149
|
+
# REMOVED before this change landed. It had zero production callers -- only its
|
|
150
|
+
# own tests -- which is exactly the dead-dispatch shape #688 deleted from this
|
|
151
|
+
# same file (see below). The gates do NOT route through it: pre-commit collects
|
|
152
|
+
# languages with its own `_detect_ext_lang` into `PROJECT_LANGS`, and that is
|
|
153
|
+
# what actually decides which adapters run (AC-717-13/13b pin the dual-language
|
|
154
|
+
# case). Keeping a second, unconsumed collector beside it would have meant two
|
|
155
|
+
# language-detection paths drifting apart, with the untested one looking
|
|
156
|
+
# authoritative. If multi-language routing is ever reworked, the change belongs
|
|
157
|
+
# in pre-commit's collector, not in a parallel helper here.
|
|
158
|
+
|
|
107
159
|
# ---------------------------------------------------------------------------
|
|
108
160
|
# REMOVED in #688: route_to_adapter()
|
|
109
161
|
#
|
|
@@ -210,7 +262,8 @@ detect_iac_project() {
|
|
|
210
262
|
|
|
211
263
|
# Check for Kubernetes manifests (YAML with apiVersion/kind)
|
|
212
264
|
if [ -n "$(find . -maxdepth 2 \( -name "*.yaml" -o -name "*.yml" \) -not -path "./.git/*" 2>/dev/null | sed -n '1p; 1q')" ]; then
|
|
213
|
-
local yaml_file
|
|
265
|
+
local yaml_file
|
|
266
|
+
yaml_file=$(find . -maxdepth 2 \( -name "*.yaml" -o -name "*.yml" \) -not -path "./.git/*" 2>/dev/null | sed -n '1p; 1q')
|
|
214
267
|
if grep -qE "^(apiVersion|kind):" "$yaml_file" 2>/dev/null; then
|
|
215
268
|
has_iac=true
|
|
216
269
|
fi
|
package/hooks/gate-10.sh
CHANGED
|
@@ -170,12 +170,25 @@ EOF
|
|
|
170
170
|
# --no-verify, which is worse than any verdict it could have produced.
|
|
171
171
|
# `-k` after the primary signal: pysemgrep spinning in a syscall ignores
|
|
172
172
|
# SIGTERM, and the shipped 0.29 code used the same `-k 10` shape.
|
|
173
|
+
# #717: p/security-audit carries NO C# rules. Measured on a .cs file with
|
|
174
|
+
# a textbook SQL injection (string-concatenated SqlCommand) plus a
|
|
175
|
+
# Process.Start command injection: p/security-audit 0 findings, p/csharp 1
|
|
176
|
+
# ERROR (csharp-sqli). Line 69 has listed `cs` in the filter all along, so
|
|
177
|
+
# .cs was accepted and then scanned by a ruleset that cannot see C# -- the
|
|
178
|
+
# identical "filter looks like coverage" defect this issue removed from
|
|
179
|
+
# Gate 4. p/csharp is loaded IN ADDITION so the security-audit rules still
|
|
180
|
+
# cover the other languages in a mixed commit.
|
|
181
|
+
SEMGREP_RULES=(--config=p/security-audit)
|
|
182
|
+
if printf '%s
|
|
183
|
+
' "$SEMGREP_FILES" | grep -qE ".cs$"; then
|
|
184
|
+
SEMGREP_RULES+=(--config=p/csharp)
|
|
185
|
+
fi
|
|
173
186
|
SEMGREP_TIMEOUT_S="${XP_GATE_SEMGREP_TIMEOUT:-180}"
|
|
174
187
|
SEMGREP_TIMED_OUT=false
|
|
175
188
|
if command -v timeout >/dev/null 2>&1; then
|
|
176
189
|
SEMGREP_STDOUT_FILE=$(mktemp)
|
|
177
190
|
SEMGREP_OUTPUT=$(timeout -k 10 "$SEMGREP_TIMEOUT_S" \
|
|
178
|
-
"$SEMGREP_CMD" scan
|
|
191
|
+
"$SEMGREP_CMD" scan "${SEMGREP_RULES[@]}" --json --disable-version-check \
|
|
179
192
|
"${SEMGREP_ARGS[@]}" 2>&1 >"$SEMGREP_STDOUT_FILE")
|
|
180
193
|
SEMGREP_EXIT=$?
|
|
181
194
|
# 124 = timeout's own "I gave up" code. 137 = 128+SIGKILL, which is what
|
|
@@ -194,7 +207,7 @@ EOF
|
|
|
194
207
|
# trade -- but the unbounded window is now stated.
|
|
195
208
|
echo " ⚠️ 'timeout' command not available — semgrep runs UNBOUNDED (install coreutils or set up gtimeout)"
|
|
196
209
|
SEMGREP_STDOUT_FILE=$(mktemp)
|
|
197
|
-
SEMGREP_OUTPUT=$("$SEMGREP_CMD" scan
|
|
210
|
+
SEMGREP_OUTPUT=$("$SEMGREP_CMD" scan "${SEMGREP_RULES[@]}" --json --disable-version-check \
|
|
198
211
|
"${SEMGREP_ARGS[@]}" 2>&1 >"$SEMGREP_STDOUT_FILE")
|
|
199
212
|
SEMGREP_EXIT=$?
|
|
200
213
|
fi
|
|
@@ -299,7 +312,9 @@ console.log("");});
|
|
|
299
312
|
echo " ❌ BLOCKED — blocking-severity vulnerability found"
|
|
300
313
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
301
314
|
echo "$FINDING_DETAILS"
|
|
302
|
-
|
|
315
|
+
# #717: a C# finding comes from p/csharp; naming only security-audit
|
|
316
|
+
# would send the author to a ruleset that reproduces zero hits.
|
|
317
|
+
echo " Run 'semgrep scan --config=p/security-audit --config=p/csharp' to review all findings."
|
|
303
318
|
GATE_10_STATUS="FAIL"
|
|
304
319
|
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
305
320
|
# shellcheck disable=SC2034
|
|
@@ -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
|