@boyingliu01/xp-gate 0.32.2 → 0.33.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/adapters/gate-3.sh +483 -28
- package/adapters/gate-4.sh +20 -0
- package/adapters/gate-7.sh +4 -0
- package/bin/xp-gate.js +50 -4
- package/gate-4.sh +8 -0
- package/hooks/gate-4.sh +8 -0
- package/hooks/post-commit +82 -0
- package/hooks/pre-commit +19 -0
- package/hooks/pre-push +21 -2
- package/lib/__tests__/check-changed-brief.test.js +63 -0
- package/lib/__tests__/doctor-audit-ledger.test.js +61 -0
- package/lib/__tests__/gate-audit.test.ts +144 -0
- package/lib/__tests__/shared-utils.test.js +22 -0
- package/lib/check.js +114 -2
- package/lib/doctor.js +59 -1
- package/lib/gate-audit.ts +215 -7
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/shared-utils.js +4 -1
- package/mock-policy/AGENTS.md +2 -2
- package/mutation/AGENTS.md +2 -2
- 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/sprint-flow/references/phase-3-build.md +47 -0
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/scripts/copy-client.cjs +6 -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/sprint-flow/references/phase-3-build.md +47 -0
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/dsh/src/contract.test.ts +25 -1
- 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/sprint-flow/references/phase-3-build.md +47 -0
- 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/sprint-flow/references/phase-3-build.md +47 -0
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
- package/principles/AGENTS.md +2 -2
- package/scripts/sync-package-content.js +1 -1
- package/skills/delphi-review/AGENTS.md +2 -2
- package/skills/sprint-flow/AGENTS.md +2 -2
- package/skills/sprint-flow/references/phase-3-build.md +47 -0
- package/skills/test-specification-alignment/AGENTS.md +2 -2
package/adapters/gate-3.sh
CHANGED
|
@@ -8,21 +8,297 @@
|
|
|
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
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
# #537/#598: this arm used to be a hard SKIP with "no PowerShell tool". That
|
|
24
|
+
# was true of lizard (it cannot parse .ps1) but NOT of the platform:
|
|
25
|
+
# PowerShell's own AST gives an exact decision-point count, which is the same
|
|
26
|
+
# quantity lizard measures for other languages. A PowerShell-primary repo
|
|
27
|
+
# therefore had a permanently unmeasured quality dimension while the score
|
|
28
|
+
# implied full coverage.
|
|
29
|
+
#
|
|
30
|
+
# The measurement lives in lib/Measure-PowerShellComplexity.ps1 because the
|
|
31
|
+
# hook cannot know a project's refactor history; the adapter only runs it and
|
|
32
|
+
# translates the verdict.
|
|
33
|
+
#
|
|
34
|
+
# The threshold resolution is duplicated from the lizard arm on purpose: the
|
|
35
|
+
# arms are read independently of each other, and a shared helper here would
|
|
36
|
+
# make the PowerShell path depend on the code path it replaces.
|
|
37
|
+
_gate3_ps_threshold() {
|
|
38
|
+
local default=5 val
|
|
39
|
+
if [ -f ".xp-gate/ccn-threshold" ]; then
|
|
40
|
+
val=$(sed -n '1s/[[:space:]]*$//p' ".xp-gate/ccn-threshold" 2>/dev/null || true)
|
|
41
|
+
case "$val" in
|
|
42
|
+
''|*[!0-9]*) echo "WARN - invalid .xp-gate/ccn-threshold value '${val}' (expected a positive integer), falling back to ${default}" >&2 ;;
|
|
43
|
+
0) echo "WARN - invalid .xp-gate/ccn-threshold value '0' (expected a positive integer), falling back to ${default}" >&2 ;;
|
|
44
|
+
*) echo "$val"; return 0 ;;
|
|
45
|
+
esac
|
|
46
|
+
fi
|
|
47
|
+
val=${XP_GATE_CCN_THRESHOLD:-}
|
|
48
|
+
case "$val" in
|
|
49
|
+
'') echo "$default" ;;
|
|
50
|
+
*[!0-9]*) echo "WARN - invalid XP_GATE_CCN_THRESHOLD value '${val}' (expected a positive integer), falling back to ${default}" >&2; echo "$default" ;;
|
|
51
|
+
0) echo "WARN - invalid XP_GATE_CCN_THRESHOLD value '0' (expected a positive integer), falling back to ${default}" >&2; echo "$default" ;;
|
|
52
|
+
*) echo "$val" ;;
|
|
53
|
+
esac
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Resolve a PowerShell engine LOCALLY.
|
|
57
|
+
#
|
|
58
|
+
# This arm must NOT call _detect_pwsh, which is defined in
|
|
59
|
+
# adapters/powershell.sh: the hook sources that adapter inside a SUBSHELL for
|
|
60
|
+
# Gate 1 --
|
|
61
|
+
# PS_G1_OUTPUT=$(source "$POWERSHELL_ADAPTER"; run_static_analysis)
|
|
62
|
+
# -- so its functions never reach this gate. Depending on it printed
|
|
63
|
+
# "no PowerShell engine found" on a machine with both pwsh 7 AND PS 5.1
|
|
64
|
+
# installed, i.e. the verdict was about bash scoping, not about the engine.
|
|
65
|
+
# A gate must not depend on another gate having sourced a library first.
|
|
66
|
+
_gate3_detect_pwsh() {
|
|
67
|
+
if command -v pwsh >/dev/null 2>&1; then echo pwsh; return 0; fi
|
|
68
|
+
if command -v powershell.exe >/dev/null 2>&1; then echo powershell.exe; return 0; fi
|
|
69
|
+
local _c
|
|
70
|
+
for _c in \
|
|
71
|
+
"${LOCALAPPDATA:-$HOME/AppData/Local}/Microsoft/WindowsApps/pwsh.exe" \
|
|
72
|
+
"/c/Program Files/PowerShell/7/pwsh.exe" \
|
|
73
|
+
"/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe"; do
|
|
74
|
+
[ -x "$_c" ] && { echo "$_c"; return 0; }
|
|
75
|
+
done
|
|
76
|
+
return 1
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
PS_CC_SCRIPT="${AUDIT_SCRIPT_DIR}/lib/Measure-PowerShellComplexity.ps1"
|
|
80
|
+
if [ ! -f "$PS_CC_SCRIPT" ]; then
|
|
81
|
+
echo "ℹ️ No PowerShell complexity tool available"
|
|
82
|
+
echo " (expected at lib/Measure-PowerShellComplexity.ps1)"
|
|
83
|
+
echo "⏭️ SKIPPED - Complexity (no PowerShell tool)"
|
|
84
|
+
GATE_3_STATUS="SKIP"
|
|
85
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
86
|
+
# shellcheck disable=SC2034
|
|
87
|
+
GATE_3_REASON="no_powershell_tool"
|
|
88
|
+
else
|
|
89
|
+
# #581: pwsh.exe is a NATIVE binary and cannot open the POSIX path this hook
|
|
90
|
+
# holds (`/c/Users/...` or `/tmp/...`). Handed one unchanged it resolves the
|
|
91
|
+
# leading `/` to `<current drive>:\`, silently measures zero files, and the
|
|
92
|
+
# gate then reports "no functions" -- an environment fault reported as a
|
|
93
|
+
# verdict about the repository. Convert the script path once, here.
|
|
94
|
+
PS_CC_SCRIPT_NATIVE="$PS_CC_SCRIPT"
|
|
95
|
+
if command -v to_native_path >/dev/null 2>&1; then
|
|
96
|
+
PS_CC_SCRIPT_NATIVE=$(to_native_path "$PS_CC_SCRIPT" 2>/dev/null || echo "$PS_CC_SCRIPT")
|
|
97
|
+
fi
|
|
98
|
+
PS_CC_SCRIPT="$PS_CC_SCRIPT_NATIVE"
|
|
99
|
+
PS_CC_PWSH=$(_gate3_detect_pwsh 2>/dev/null || true)
|
|
100
|
+
if [ -z "$PS_CC_PWSH" ]; then
|
|
101
|
+
echo "⚠️ WARN - no PowerShell engine found; complexity not measured"
|
|
102
|
+
GATE_3_STATUS="WARN"
|
|
103
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
104
|
+
# shellcheck disable=SC2034
|
|
105
|
+
GATE_3_REASON="no_powershell_engine"
|
|
106
|
+
else
|
|
107
|
+
PS_CC_THRESHOLD=$(_gate3_ps_threshold)
|
|
108
|
+
PS_ADDED=$(git diff --cached --name-only --diff-filter=A 2>/dev/null | grep -E '\.ps(m)?1$' || true)
|
|
109
|
+
PS_BASELINE=".xp-gate-powershell-ccn-baseline.json"
|
|
110
|
+
|
|
111
|
+
if [ -z "$PS_ADDED" ]; then
|
|
112
|
+
# No added files, but modified ones can still get MORE complex. That is
|
|
113
|
+
# what the ratchet baseline is for; without a baseline there is nothing
|
|
114
|
+
# to compare, and silently passing would be the #584 failure mode.
|
|
115
|
+
PS_TOUCHED=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null | grep -E '\.ps(m)?1$' || true)
|
|
116
|
+
PS_TOUCHED=$(printf '%s\n' "$PS_TOUCHED" | grep -vE '(^|/)tests/' || true)
|
|
117
|
+
if [ -z "$PS_TOUCHED" ] || [ ! -f "$PS_BASELINE" ]; then
|
|
118
|
+
echo "⏭️ SKIPPED - Complexity (no added PowerShell files to measure)."
|
|
119
|
+
GATE_3_STATUS="SKIP"
|
|
120
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
121
|
+
# shellcheck disable=SC2034
|
|
122
|
+
GATE_3_REASON="no_added_powershell_files"
|
|
123
|
+
else
|
|
124
|
+
PS_CC_ARGS=()
|
|
125
|
+
for _f in $PS_TOUCHED; do PS_CC_ARGS[${#PS_CC_ARGS[@]}]="'$_f'"; done
|
|
126
|
+
|
|
127
|
+
# #598: the ratchet branch must use the SAME threshold as the baseline
|
|
128
|
+
# collector. -Threshold 1 used to make every function with CCN >= 2
|
|
129
|
+
# print OVER, so a brand-new well-factored function (CCN 2-4, below the
|
|
130
|
+
# real threshold) was diffed against a baseline that can never contain
|
|
131
|
+
# it and reported as a "regression": the gate blocked exactly the shape
|
|
132
|
+
# it exists to encourage. -List additionally emits the full population,
|
|
133
|
+
# so the comparison covers functions that are below the threshold on
|
|
134
|
+
# both sides.
|
|
135
|
+
PS_CC_OUT=$("$PS_CC_PWSH" -NoProfile -NonInteractive -ExecutionPolicy Bypass \
|
|
136
|
+
-Command "& '$PS_CC_SCRIPT' -Path @($(IFS=','; echo "${PS_CC_ARGS[*]}")) -Threshold ${PS_CC_THRESHOLD} -List" 2>&1 || true)
|
|
137
|
+
|
|
138
|
+
PS_CC_MEASURED=$(printf '%s\n' "$PS_CC_OUT" | sed -n 's/^functions_measured=//p' | tail -1)
|
|
139
|
+
if [ -z "$PS_CC_MEASURED" ] || [ "${PS_CC_MEASURED:-0}" -eq 0 ]; then
|
|
140
|
+
echo "⏭️ SKIPPED - Complexity (no functions in changed PowerShell files)."
|
|
141
|
+
GATE_3_STATUS="SKIP"
|
|
142
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
143
|
+
# shellcheck disable=SC2034
|
|
144
|
+
GATE_3_REASON="no_functions_found"
|
|
145
|
+
else
|
|
146
|
+
PS_CC_NODE=""
|
|
147
|
+
if command -v node >/dev/null 2>&1; then PS_CC_NODE="node"; fi
|
|
148
|
+
if [ -z "$PS_CC_NODE" ]; then
|
|
149
|
+
echo "ℹ️ node unavailable — complexity measured (${PS_CC_MEASURED} function(s)) but ratchet not compared"
|
|
150
|
+
echo "✅ PASSED - PowerShell complexity measured (ratchet unavailable)."
|
|
151
|
+
GATE_3_DETAIL="mode=measured_no_ratchet"
|
|
152
|
+
else
|
|
153
|
+
# Compare every measured function against the baseline. A function
|
|
154
|
+
# absent from the baseline is NEW: it is only a regression if it is
|
|
155
|
+
# above the threshold, which is exactly what the old -Threshold 1
|
|
156
|
+
# comparison got wrong. An unreadable baseline is ERR, not a pass.
|
|
157
|
+
PS_CC_VERDICT=$(printf '%s\n' "$PS_CC_OUT" | "$PS_CC_NODE" -e '
|
|
158
|
+
let raw = "";
|
|
159
|
+
process.stdin.on("data", (c) => { raw += c; });
|
|
160
|
+
process.stdin.on("end", () => {
|
|
161
|
+
const fs = require("fs");
|
|
162
|
+
const fsPath = process.argv[1];
|
|
163
|
+
const threshold = Number(process.argv[2]);
|
|
164
|
+
let baseline;
|
|
165
|
+
try {
|
|
166
|
+
baseline = JSON.parse(fs.readFileSync(fsPath, "utf8"));
|
|
167
|
+
} catch (e) {
|
|
168
|
+
console.log("ERR " + e.message);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const known = new Map();
|
|
172
|
+
const entries = baseline.functions || baseline.entries || baseline;
|
|
173
|
+
if (Array.isArray(entries)) {
|
|
174
|
+
for (const e of entries) {
|
|
175
|
+
if (!e || !e.file || !e.name) continue;
|
|
176
|
+
known.set(norm(e.file) + "::" + e.name, Number(e.ccn) || 0);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// The measurement emits ABSOLUTE paths (pwsh resolved them), while
|
|
180
|
+
// a baseline recorded by a repo-side generator holds paths relative
|
|
181
|
+
// to the repo root. Normalising alone cannot bridge that, so a key
|
|
182
|
+
// miss falls back to a suffix match on the file path: exactly one
|
|
183
|
+
// known entry whose normalised path ends with "<sep>measured" is the
|
|
184
|
+
// same file. Without this every existing function looked brand new,
|
|
185
|
+
// and a real regression was reported as "new function" -- the right
|
|
186
|
+
// verdict for the wrong reason.
|
|
187
|
+
//
|
|
188
|
+
// Backslashes are normalised with split/join, NOT -replace: this JS
|
|
189
|
+
// is embedded in a double-quoted bash command, where a regex escape
|
|
190
|
+
// loses a level and silently matches the wrong number of
|
|
191
|
+
// backslashes. Measured: /\\\\/g arrived as /\\/g, matched only
|
|
192
|
+
// doubled separators, and every Windows path kept its backslashes.
|
|
193
|
+
function norm(p) {
|
|
194
|
+
return String(p)
|
|
195
|
+
.split("\\").join("/")
|
|
196
|
+
.replace(/^[A-Za-z]:\//, "")
|
|
197
|
+
.replace(/^\.\//, "")
|
|
198
|
+
.replace(/^\//, "");
|
|
199
|
+
}
|
|
200
|
+
function lookup(path, name) {
|
|
201
|
+
const key = norm(path) + "::" + name;
|
|
202
|
+
if (known.has(key)) return known.get(key);
|
|
203
|
+
const target = norm(path);
|
|
204
|
+
let hit = null;
|
|
205
|
+
for (const [k, v] of known) {
|
|
206
|
+
const [kp, kn] = k.split("::");
|
|
207
|
+
if (kn !== name) continue;
|
|
208
|
+
if (target.endsWith("/" + kp) || kp.endsWith("/" + target) || kp === target) {
|
|
209
|
+
if (hit !== null && hit !== v) return null; // ambiguous: treat as new
|
|
210
|
+
hit = v;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return hit;
|
|
214
|
+
}
|
|
215
|
+
const seen = new Set();
|
|
216
|
+
const regressions = [];
|
|
217
|
+
const improved = [];
|
|
218
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
219
|
+
const m = line.match(/^(?:OVER|FUNC) (.+):(\d+) (\S+) ccn=(\d+)$/);
|
|
220
|
+
if (!m) continue;
|
|
221
|
+
const key = norm(m[1]) + "::" + m[3];
|
|
222
|
+
const now = Number(m[4]);
|
|
223
|
+
seen.add(key);
|
|
224
|
+
const was = lookup(m[1], m[3]);
|
|
225
|
+
if (was === null) {
|
|
226
|
+
if (now > threshold) {
|
|
227
|
+
regressions.push(m[1] + ":" + m[2] + " " + m[3] + " new function at ccn=" + now);
|
|
228
|
+
}
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (now > was) {
|
|
232
|
+
regressions.push(m[1] + ":" + m[2] + " " + m[3] + " ccn " + was + " -> " + now);
|
|
233
|
+
} else if (now < was) {
|
|
234
|
+
improved.push(m[1] + ":" + m[2] + " " + m[3] + " ccn " + was + " -> " + now);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
for (const r of regressions) console.log("REGRESSION " + r);
|
|
238
|
+
for (const i of improved) console.log("IMPROVED " + i);
|
|
239
|
+
if (regressions.length > 0) {
|
|
240
|
+
console.log("FAIL " + regressions.length + " function(s) increased cyclomatic complexity vs " + fsPath + ".");
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
' "$PS_BASELINE" "$PS_CC_THRESHOLD" 2>&1 || true)
|
|
244
|
+
|
|
245
|
+
if printf '%s\n' "$PS_CC_VERDICT" | grep -q '^FAIL '; then
|
|
246
|
+
PS_CC_REG=$(printf '%s\n' "$PS_CC_VERDICT" | sed -n 's/^FAIL //p' | tail -1)
|
|
247
|
+
printf '%s\n' "$PS_CC_VERDICT" | grep '^REGRESSION ' | sed 's/^REGRESSION / ❌ /'
|
|
248
|
+
echo "❌ BLOCKED - ${PS_CC_REG}"
|
|
249
|
+
GATE_3_STATUS="BLOCK"
|
|
250
|
+
GATE_3_REASON="ccn_ratchet_regression"
|
|
251
|
+
exit 1
|
|
252
|
+
elif printf '%s\n' "$PS_CC_VERDICT" | grep -q '^ERR '; then
|
|
253
|
+
echo "⚠️ WARN - complexity ratchet baseline unreadable: $(printf '%s\n' "$PS_CC_VERDICT" | sed -n 's/^ERR //p' | head -1)"
|
|
254
|
+
GATE_3_STATUS="WARN"
|
|
255
|
+
GATE_3_REASON="baseline_unreadable"
|
|
256
|
+
else
|
|
257
|
+
PS_CC_IMPROVED=$(printf '%s\n' "$PS_CC_VERDICT" | grep -c '^IMPROVED ' || true)
|
|
258
|
+
printf '%s\n' "$PS_CC_VERDICT" | grep '^IMPROVED ' | sed 's/^IMPROVED / 📉 /'
|
|
259
|
+
echo "✅ PASSED - PowerShell complexity within ratchet (${PS_CC_MEASURED} function(s), ${PS_CC_IMPROVED} improved, threshold ${PS_CC_THRESHOLD})."
|
|
260
|
+
GATE_3_STATUS="PASS"
|
|
261
|
+
GATE_3_DETAIL="powerShell_ratchet improved=${PS_CC_IMPROVED}"
|
|
262
|
+
fi
|
|
263
|
+
fi
|
|
264
|
+
fi
|
|
265
|
+
fi
|
|
266
|
+
else
|
|
267
|
+
# Freshly added files: this arm has no history to compare, so it judges
|
|
268
|
+
# each file's functions against the threshold. A file that is merely
|
|
269
|
+
# being touched keeps its history (that is what the baseline is for);
|
|
270
|
+
# measuring only ADDED files means the arm can never retroactively block
|
|
271
|
+
# a cleanup commit.
|
|
272
|
+
PS_CC_ARGS=()
|
|
273
|
+
for _f in $PS_ADDED; do PS_CC_ARGS[${#PS_CC_ARGS[@]}]="'$_f'"; done
|
|
274
|
+
PS_CC_OUT=$("$PS_CC_PWSH" -NoProfile -NonInteractive -ExecutionPolicy Bypass \
|
|
275
|
+
-Command "& '$PS_CC_SCRIPT' -Path @($(IFS=','; echo "${PS_CC_ARGS[*]}")) -Threshold ${PS_CC_THRESHOLD}" 2>&1 || true)
|
|
276
|
+
PS_CC_MEASURED=$(printf '%s\n' "$PS_CC_OUT" | sed -n 's/^functions_measured=//p' | tail -1)
|
|
277
|
+
PS_CC_OVER=$(printf '%s\n' "$PS_CC_OUT" | sed -n 's/^over_threshold=//p' | tail -1)
|
|
278
|
+
PS_CC_MAX=$(printf '%s\n' "$PS_CC_OUT" | sed -n 's/^max_ccn=//p' | tail -1)
|
|
279
|
+
if [ -z "$PS_CC_MEASURED" ] || [ "${PS_CC_MEASURED:-0}" -eq 0 ]; then
|
|
280
|
+
echo "⏭️ SKIPPED - Complexity (no functions in added PowerShell files)."
|
|
281
|
+
GATE_3_STATUS="SKIP"
|
|
282
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
283
|
+
# shellcheck disable=SC2034
|
|
284
|
+
GATE_3_REASON="no_functions_found"
|
|
285
|
+
elif [ "${PS_CC_OVER:-0}" -gt 0 ]; then
|
|
286
|
+
echo " Measured ${PS_CC_MEASURED} function(s) via AST; max CCN ${PS_CC_MAX} (threshold ${PS_CC_THRESHOLD})"
|
|
287
|
+
printf '%s\n' "$PS_CC_OUT" | grep '^OVER ' | sed 's/^/ /'
|
|
288
|
+
echo "❌ BLOCKED - ${PS_CC_OVER} newly added function(s) with CCN > ${PS_CC_THRESHOLD}."
|
|
289
|
+
echo "Refactor high-complexity functions, or raise the threshold deliberately."
|
|
290
|
+
GATE_3_STATUS="BLOCK"
|
|
291
|
+
GATE_3_REASON="ccn_over_threshold"
|
|
292
|
+
exit 1
|
|
293
|
+
else
|
|
294
|
+
echo "✅ PASSED - All added PowerShell functions within complexity threshold (${PS_CC_THRESHOLD})."
|
|
295
|
+
echo " Measured ${PS_CC_MEASURED} function(s) via AST; max CCN ${PS_CC_MAX}."
|
|
296
|
+
GATE_3_STATUS="PASS"
|
|
297
|
+
fi
|
|
298
|
+
fi
|
|
299
|
+
fi
|
|
300
|
+
fi
|
|
301
|
+
|
|
26
302
|
else
|
|
27
303
|
# S2 (#507): three-level CCN threshold override --
|
|
28
304
|
# .xp-gate/ccn-threshold (positive integer) > XP_GATE_CCN_THRESHOLD > 5.
|
|
@@ -77,40 +353,209 @@ else
|
|
|
77
353
|
|
|
78
354
|
# Get changed source files for complexity check (includes test files)
|
|
79
355
|
CC_FILES=$(echo "$CHANGED_FILES" | grep -E '\.(ts|tsx|js|jsx|py|go|java|swift|cpp|c|hpp|h|m|mm|kt)$' || true)
|
|
80
|
-
|
|
356
|
+
|
|
357
|
+
# Grade exactly the population the baseline was written from. The two must
|
|
358
|
+
# match or the ratchet reports pre-existing code as "new", which is a false
|
|
359
|
+
# BLOCK no repair can clear (the exact failure #495 Round 3 called out, in a
|
|
360
|
+
# different place). scripts/ccn-report.cjs --write-baseline records only
|
|
361
|
+
# bucket === 'production' rows; its bucketFor() classifies two other buckets:
|
|
362
|
+
# mirror src/npm-package/**, plugins/*/src/** — regenerated by prepack
|
|
363
|
+
# from a canonical source that IS graded here. Editing a mirror
|
|
364
|
+
# is reverted at the next publish, so grading it cannot be acted
|
|
365
|
+
# on (doctorDelphi CCN=106 blocked every src/npm-package commit,
|
|
366
|
+
# on HEAD too — no baseline entry could ever exist for it).
|
|
367
|
+
# test test dirs (tests/, test/, __tests__/), *.test.*, test_*.py,
|
|
368
|
+
# *_test.{py,go}, *Test(s).java — recorded in the report's test
|
|
369
|
+
# bucket, never in the baseline.
|
|
370
|
+
# Both prefixes and the test pattern mirror ccn-report.cjs so the gate and
|
|
371
|
+
# the writer cannot drift apart again (pinned by __tests__/gate-3-*.bats).
|
|
372
|
+
#
|
|
373
|
+
# #634: pytest's default is `python_files = test_*.py` under a `tests/`
|
|
374
|
+
# root. The previous pattern only knew the SUFFIX form `_test.{py,go}` and
|
|
375
|
+
# `*.test.*`, so a pytest-laid-out project had every test module graded as
|
|
376
|
+
# production source: test-helper complexity was reported as source debt and
|
|
377
|
+
# the ratchet could block on files the baseline deliberately never records
|
|
378
|
+
# (bucket === 'test').
|
|
379
|
+
# `(^|/)(tests?)/` — a tests/ or test/ directory anywhere in the tree
|
|
380
|
+
# `(^|/)test_[^/]*\.py$` — pytest default filenames at any depth
|
|
381
|
+
# Both are anchored with (^|/) so `latest/`, `contest/`, `attest/` and
|
|
382
|
+
# `my_test_dir/` are NOT caught (a leading `_?test_.*\.py$` would have
|
|
383
|
+
# swallowed `src/my_test_dir/bar.py`).
|
|
384
|
+
# Must stay equivalent to TEST_PATH in scripts/ccn-report.cjs.
|
|
385
|
+
CC_FILES=$(echo "$CC_FILES" | grep -vE \
|
|
386
|
+
'^"?src/npm-package/|^"?plugins/[^/]+/src/|(^|/)__tests?__/|(^|/)(tests?)/|\.test\.[cm]?[jt]sx?$|(^|/)test_[^/]*\.py$|_test\.(py|go)$|Test(s)?\.java$' || true)
|
|
387
|
+
|
|
81
388
|
if [ -n "$CC_FILES" ]; then
|
|
82
389
|
echo "Checking complexity for source files..."
|
|
83
390
|
|
|
84
|
-
# Run lizard
|
|
85
|
-
# One path per line, into an array. Unquoted $CC_FILES relied on word
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
391
|
+
# Run lizard in chunks. Two reasons, both load-bearing:
|
|
392
|
+
# - One path per line, into an array. Unquoted $CC_FILES relied on word
|
|
393
|
+
# splitting, which also splits any path containing a space -- handing
|
|
394
|
+
# lizard two bogus paths and checking neither (#457 defect class, same
|
|
395
|
+
# fix as gate-4's PRINCIPLES_ARGS). Quoting the scalar would instead
|
|
396
|
+
# collapse the list into one argument. An array does both.
|
|
397
|
+
# - Chunked instead of one argv. Windows caps a single command line at
|
|
398
|
+
# 32,767 characters, and under `--all` the population is the whole tree
|
|
399
|
+
# (1,000+ sources on a mid-sized repo). The oversized argv never reached
|
|
400
|
+
# lizard, nothing was printed, and the gate graded an empty CSV as if the
|
|
401
|
+
# tree were clean. XP_GATE_LIZARD_ARGV_BUDGET lets a probe force a split
|
|
402
|
+
# without needing a huge fixture.
|
|
403
|
+
LIZARD_ARGV_BUDGET="${XP_GATE_LIZARD_ARGV_BUDGET:-8000}"
|
|
404
|
+
lizard_batches() {
|
|
405
|
+
# stdin: one path per line. stdout: lizard output, batches concatenated.
|
|
406
|
+
local _batch=() _size=0 _path
|
|
407
|
+
while IFS= read -r _path; do
|
|
408
|
+
[ -n "$_path" ] || continue
|
|
409
|
+
if [ "${#_batch[@]}" -gt 0 ] && [ $((_size + ${#_path} + 1)) -gt "$LIZARD_ARGV_BUDGET" ]; then
|
|
410
|
+
"$LIZARD_PATH" "$@" "${_batch[@]}" 2>/dev/null || true
|
|
411
|
+
_batch=()
|
|
412
|
+
_size=0
|
|
413
|
+
fi
|
|
414
|
+
_batch+=("$_path")
|
|
415
|
+
_size=$((_size + ${#_path} + 1))
|
|
416
|
+
done
|
|
417
|
+
if [ "${#_batch[@]}" -gt 0 ]; then
|
|
418
|
+
"$LIZARD_PATH" "$@" "${_batch[@]}" 2>/dev/null || true
|
|
419
|
+
fi
|
|
420
|
+
return 0
|
|
421
|
+
}
|
|
422
|
+
CC_OUTPUT=$(printf '%s\n' "$CC_FILES" | lizard_batches -C "$CCN_THRESHOLD" 2>&1)
|
|
423
|
+
|
|
424
|
+
# REQ-D (sprint-20261018-quality): baseline ratchet. When
|
|
425
|
+
# .ccn-baseline.json exists it replaces the flat threshold verdict:
|
|
426
|
+
# recorded functions may not get WORSE (strictly greater blocks), new
|
|
427
|
+
# functions face the live thresholds, improvements only hint. The
|
|
428
|
+
# legacy path below stays byte-for-byte for repos without a baseline.
|
|
429
|
+
if [ -f ".ccn-baseline.json" ]; then
|
|
430
|
+
# #544: lizard's raw CCN is not what the baseline was recorded with.
|
|
431
|
+
# The baseline comes from scripts/ccn-report.cjs, which re-derives every
|
|
432
|
+
# above-threshold CCN from brace-balanced source (lizard's TS parser
|
|
433
|
+
# loses braces on generic-arrow fields and charges neighbours). Feeding
|
|
434
|
+
# the ratchet raw numbers compared two different engines — a function
|
|
435
|
+
# at its recorded ceiling read as "worse" purely from span inflation.
|
|
436
|
+
# Route the CSV through the same verifier; on rows it cannot verify the
|
|
437
|
+
# helper exits non-zero, and this gate fails closed instead of passing
|
|
438
|
+
# an unvalidated number.
|
|
439
|
+
CC_CSV=$(printf '%s\n' "$CC_FILES" | lizard_batches --csv)
|
|
440
|
+
CC_REPORT="$(git rev-parse --show-toplevel 2>/dev/null)/scripts/ccn-report.cjs"
|
|
441
|
+
# Fail closed when the verifier is unavailable. Previously this fell
|
|
442
|
+
# through and graded the RAW lizard CSV, while CI hard-fails on the same
|
|
443
|
+
# condition — so the hook and CI could reach opposite verdicts on
|
|
444
|
+
# identical code, and the hook silently compared baseline numbers
|
|
445
|
+
# (recorded by ccn-report's brace-verified engine) against an engine it
|
|
446
|
+
# no longer had. Agreeing with CI is the point of having both.
|
|
447
|
+
if [ ! -f "$CC_REPORT" ] || ! command -v node >/dev/null 2>&1; then
|
|
448
|
+
echo "❌ BLOCKED - complexity verifier unavailable (scripts/ccn-report.cjs needs node)."
|
|
449
|
+
echo "CI fails on this same condition; refusing to grade unverified CCN locally."
|
|
450
|
+
GATE_3_DETAIL="mode=ratchet verifier=MISSING"
|
|
451
|
+
exit 1
|
|
452
|
+
fi
|
|
453
|
+
if [ -f "$CC_REPORT" ] && command -v node >/dev/null 2>&1; then
|
|
454
|
+
_cc_verify_failed=0
|
|
455
|
+
CC_CSV_VERIFIED=$(printf '%s\n' "$CC_CSV" | node "$CC_REPORT" --csv-verified batch --root "$(git rev-parse --show-toplevel 2>/dev/null)") || _cc_verify_failed=1
|
|
456
|
+
if [ "$_cc_verify_failed" -ne 0 ]; then
|
|
457
|
+
echo "❌ BLOCKED - complexity could not be span-verified; refusing to grade unverified CCN."
|
|
458
|
+
GATE_3_DETAIL="mode=ratchet verify=FAILED"
|
|
459
|
+
exit 1
|
|
460
|
+
fi
|
|
461
|
+
CC_CSV="$CC_CSV_VERIFIED"
|
|
462
|
+
fi
|
|
463
|
+
RATCHET_LIB="$GATE_DIR/lib/ccn-ratchet.sh"
|
|
464
|
+
[ -f "$RATCHET_LIB" ] || RATCHET_LIB="$(git rev-parse --show-toplevel 2>/dev/null)/githooks/lib/ccn-ratchet.sh"
|
|
465
|
+
# Fail closed when the ratchet library is missing. Sourcing a nonexistent
|
|
466
|
+
# file only prints "No such file or directory" and leaves
|
|
467
|
+
# evaluate_ccn_ratchet undefined; the verdict then came back empty, every
|
|
468
|
+
# counter read 0 and the gate printed "✅ PASSED" — a CCN regression graded
|
|
469
|
+
# green with nothing compared. The installer never created this path
|
|
470
|
+
# (copyAdapters skipped hooks/lib), so this was the normal state after a
|
|
471
|
+
# global install, not an exotic one.
|
|
472
|
+
if [ ! -f "$RATCHET_LIB" ]; then
|
|
473
|
+
echo "❌ BLOCKED - complexity ratchet library unavailable (ccn-ratchet.sh)."
|
|
474
|
+
echo "Expected at \$GATE_DIR/lib/ccn-ratchet.sh or <repo>/githooks/lib/ccn-ratchet.sh."
|
|
475
|
+
echo "Reinstall with 'xp-gate init --global' (or 'xp-gate update-hooks') to restore it."
|
|
476
|
+
GATE_3_DETAIL="mode=ratchet ratchet=MISSING"
|
|
477
|
+
exit 1
|
|
478
|
+
fi
|
|
479
|
+
# shellcheck source=/dev/null
|
|
480
|
+
. "$RATCHET_LIB"
|
|
481
|
+
# NOTE: the thresholds must be plain assignments BEFORE the pipe. As a
|
|
482
|
+
# prefix to a variable assignment (`T=1 V=$(... | fn)`) they land in the
|
|
483
|
+
# assignment's subshell, so the function sees no thresholds AND reads an
|
|
484
|
+
# empty stdin — the verdict comes back empty and Gate 3 passes on zero
|
|
485
|
+
# rows (measured while wiring #544: same input passed or blocked
|
|
486
|
+
# depending only on where the prefix sat).
|
|
487
|
+
# Consumed by evaluate_ccn_ratchet in the sourced lib, not here.
|
|
488
|
+
# shellcheck disable=SC2034
|
|
489
|
+
CCN_WARN_THRESHOLD="$CCN_THRESHOLD"
|
|
490
|
+
# shellcheck disable=SC2034
|
|
491
|
+
CCN_BLOCK_THRESHOLD=$((CCN_THRESHOLD * 2))
|
|
492
|
+
RATCHET_VERDICT=$(printf '%s\n' "$CC_CSV" | evaluate_ccn_ratchet ".ccn-baseline.json")
|
|
493
|
+
CC_VIOLATIONS=$(printf '%s\n' "$RATCHET_VERDICT" | grep -c '^BLOCK ' || true)
|
|
494
|
+
CC_LOWER=$(printf '%s\n' "$RATCHET_VERDICT" | grep -c '^LOWER ' || true)
|
|
495
|
+
printf '%s\n' "$RATCHET_VERDICT" | grep -v '^OK ' || true
|
|
496
|
+
if [ "${CC_LOWER:-0}" -gt 0 ]; then
|
|
497
|
+
echo "ℹ️ $CC_LOWER function(s) improved below baseline — refresh with: node scripts/ccn-report.cjs --write-baseline"
|
|
498
|
+
fi
|
|
499
|
+
GATE_3_DETAIL="mode=ratchet"
|
|
500
|
+
if [ "${CC_VIOLATIONS:-0}" -gt 0 ]; then
|
|
501
|
+
echo ""
|
|
502
|
+
echo "❌ BLOCKED - $CC_VIOLATIONS function(s) violate the complexity ratchet (.ccn-baseline.json)."
|
|
503
|
+
echo "Revert the increase, or refactor; refresh the baseline only via ccn-baseline-refresh commits."
|
|
504
|
+
exit 1
|
|
505
|
+
else
|
|
506
|
+
echo "✅ PASSED - Complexity within ratchet baseline."
|
|
507
|
+
GATE_3_STATUS="PASS"
|
|
508
|
+
fi
|
|
509
|
+
else
|
|
510
|
+
# #543: count from lizard's machine-readable CSV, never its tables.
|
|
511
|
+
#
|
|
512
|
+
# The previous parser grepped '^Warning cnt' for a summary row. Two things
|
|
513
|
+
# made that never match: "Warning cnt" is a column header in the MIDDLE of
|
|
514
|
+
# a line ("Total nloc Avg.NLOC ... Warning cnt Fun Rt nloc Rt"), and
|
|
515
|
+
# lizard omits the summary block entirely when given explicit file
|
|
516
|
+
# arguments -- which is how this gate always invokes it. CC_WARNINGS was
|
|
517
|
+
# therefore always 0 and the flat-threshold path could not block anything.
|
|
518
|
+
#
|
|
519
|
+
# --csv has a stable column order (NLOC,CCN,token,PARAM,length,location,
|
|
520
|
+
# file,name,signature,...), so the verdict comes from the same numbers
|
|
521
|
+
# lizard uses, independent of version and table formatting. --csv here is
|
|
522
|
+
# the flat-threshold run; the ratchet branch above builds its own.
|
|
523
|
+
CC_CSV_FLAT=$(printf '%s\n' "$CC_FILES" | lizard_batches --csv -C "$CCN_THRESHOLD")
|
|
524
|
+
CC_WARNINGS=$(printf '%s\n' "$CC_CSV_FLAT" | awk -F, -v t="$CCN_THRESHOLD" '
|
|
525
|
+
NF > 1 && $2 + 0 > t + 0 { c++ }
|
|
526
|
+
END { print c + 0 }
|
|
527
|
+
')
|
|
528
|
+
# A lizard that produced no parsable row must not read as "clean". Without
|
|
529
|
+
# this, a crashing or mis-invoked lizard yields 0 warnings and the gate
|
|
530
|
+
# reports PASSED on zero analysed functions -- the same silent-success
|
|
531
|
+
# failure this issue is about.
|
|
532
|
+
if ! printf '%s\n' "$CC_CSV_FLAT" | awk -F, 'NF > 1 && $2 ~ /^[0-9]+$/ { found = 1; exit }
|
|
533
|
+
END { exit(found ? 0 : 1) }'; then
|
|
534
|
+
echo "⚠️ WARN - lizard produced no parsable results; complexity not verified"
|
|
535
|
+
GATE_3_STATUS="WARN"
|
|
536
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
537
|
+
# shellcheck disable=SC2034
|
|
538
|
+
GATE_3_REASON="lizard_output_unparsable"
|
|
539
|
+
elif [ "$CC_WARNINGS" -gt 0 ]; then
|
|
102
540
|
echo "$CC_OUTPUT"
|
|
103
541
|
echo ""
|
|
104
542
|
echo "❌ BLOCKED - $CC_WARNINGS functions with CCN > $CCN_THRESHOLD found."
|
|
105
543
|
echo "Refactor high-complexity functions to keep below $CCN_THRESHOLD complexity."
|
|
544
|
+
GATE_3_STATUS="BLOCK"
|
|
545
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
546
|
+
# shellcheck disable=SC2034
|
|
547
|
+
GATE_3_REASON="ccn_over_threshold"
|
|
106
548
|
exit 1
|
|
107
549
|
else
|
|
108
550
|
echo "✅ PASSED - All functions within complexity threshold ($CCN_THRESHOLD)."
|
|
109
551
|
GATE_3_STATUS="PASS"
|
|
110
552
|
fi
|
|
553
|
+
fi
|
|
111
554
|
else
|
|
112
555
|
# #589: "no source files to check" described an empty PROJECT when the real
|
|
113
|
-
# cause is an empty CHANGED_FILES
|
|
556
|
+
# cause is an empty CHANGED_FILES — an empty DIFF, or a `--all` run whose
|
|
557
|
+
# tree genuinely has no sources. The old wording read like a pass, which is
|
|
558
|
+
# why the reporter worked around the gate by running lizard by hand.
|
|
114
559
|
if [ -n "${GATE_SCOPE:-}" ] && [ "$GATE_SCOPE" = "all" ]; then
|
|
115
560
|
echo "⏭️ SKIPPED - Complexity: no tracked source files in this repository (scope: --all, whole tree)."
|
|
116
561
|
else
|
|
@@ -118,6 +563,8 @@ else
|
|
|
118
563
|
echo " Run 'xp-gate check . --all' to scan the whole tree."
|
|
119
564
|
fi
|
|
120
565
|
GATE_3_STATUS="SKIP"
|
|
566
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
567
|
+
# shellcheck disable=SC2034
|
|
121
568
|
GATE_3_REASON="no_source_files_changed"
|
|
122
569
|
fi
|
|
123
570
|
else
|
|
@@ -125,7 +572,15 @@ else
|
|
|
125
572
|
echo " Install with: pip install --user lizard"
|
|
126
573
|
echo " Gate 3: Complexity check (WARN, tool not available)"
|
|
127
574
|
GATE_3_STATUS="WARN"
|
|
575
|
+
# Consumed by the sourcing parent (pre-commit report JSON), not here.
|
|
576
|
+
# shellcheck disable=SC2034
|
|
128
577
|
GATE_3_REASON="tool_not_installed"
|
|
129
578
|
fi
|
|
130
579
|
fi
|
|
131
|
-
|
|
580
|
+
# The audit's issues_found comes from whichever mode actually ran. In ratchet
|
|
581
|
+
# mode the count lives in CC_VIOLATIONS (functions worse than their recorded
|
|
582
|
+
# baseline); the flat-threshold mode parses CC_WARNINGS from lizard's own table.
|
|
583
|
+
# Reading only CC_WARNINGS meant every ratchet block recorded issues_found=0 —
|
|
584
|
+
# the gate failed while the journal claimed nothing was found.
|
|
585
|
+
CC_ISSUES="${CC_VIOLATIONS:-${CC_WARNINGS:-0}}"
|
|
586
|
+
record_gate_audit "gate-3" "complexity" "$GATE_3_STATUS" "$CC_ISSUES" "$GATE_3_START" "ccn_threshold=${CCN_THRESHOLD:-5}${GATE_3_DETAIL:+ $GATE_3_DETAIL}"
|
package/adapters/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
|
|
@@ -103,6 +107,14 @@ else
|
|
|
103
107
|
if [ -n "$PRINCIPLES_DIR" ]; then
|
|
104
108
|
echo "Checking Clean Code + SOLID principles..."
|
|
105
109
|
|
|
110
|
+
# #581/#582 class: run_tsx drives a NATIVE node/tsx binary. A POSIX
|
|
111
|
+
# path like $HOME/.config/... reaches it as the wrong drive prefix and
|
|
112
|
+
# the checker is "not found", which the exit>=2 branch then reported
|
|
113
|
+
# as SKIPPED. Convert once here so both invocations agree.
|
|
114
|
+
if declare -F to_native_path >/dev/null 2>&1; then
|
|
115
|
+
PRINCIPLES_DIR="$(to_native_path "$PRINCIPLES_DIR")"
|
|
116
|
+
fi
|
|
117
|
+
|
|
106
118
|
# AC-507-04-02: say what a Java commit is actually covered for, instead
|
|
107
119
|
# of implying a full 15-rule check (#507). Printed before the run so it
|
|
108
120
|
# appears on both the PASS and the FAIL path.
|
|
@@ -156,6 +168,8 @@ else
|
|
|
156
168
|
echo "⚠️ Warning: Principles checker execution failed"
|
|
157
169
|
echo "⏭️ SKIPPED - Principles check (execution issue)"
|
|
158
170
|
GATE_4_STATUS="SKIP"
|
|
171
|
+
# Consumed by the sourcing parent, not here (see #548).
|
|
172
|
+
# shellcheck disable=SC2034
|
|
159
173
|
GATE_4_REASON="checker_execution_error"
|
|
160
174
|
rm -f "$PRINCIPLES_JSON"
|
|
161
175
|
else
|
|
@@ -191,17 +205,23 @@ else
|
|
|
191
205
|
echo "ℹ️ npx/tsx not available - skipping principles check"
|
|
192
206
|
echo "⏭️ SKIPPED - Principles check (no Node.js/tsx)"
|
|
193
207
|
GATE_4_STATUS="SKIP"
|
|
208
|
+
# Consumed by the sourcing parent, not here (see #548).
|
|
209
|
+
# shellcheck disable=SC2034
|
|
194
210
|
GATE_4_REASON="tool_not_installed"
|
|
195
211
|
fi
|
|
196
212
|
else
|
|
197
213
|
echo "ℹ️ Principles checker not found in project - skipping"
|
|
198
214
|
echo "⏭️ SKIPPED - Principles check (checker not in project)"
|
|
199
215
|
GATE_4_STATUS="SKIP"
|
|
216
|
+
# Consumed by the sourcing parent, not here (see #548).
|
|
217
|
+
# shellcheck disable=SC2034
|
|
200
218
|
GATE_4_REASON="checker_not_found"
|
|
201
219
|
fi
|
|
202
220
|
else
|
|
203
221
|
echo "⏭️ SKIPPED - Principles check (no matching source files changed)"
|
|
204
222
|
GATE_4_STATUS="SKIP"
|
|
223
|
+
# Consumed by the sourcing parent, not here (see #548).
|
|
224
|
+
# shellcheck disable=SC2034
|
|
205
225
|
GATE_4_REASON="no_matching_files_changed"
|
|
206
226
|
fi
|
|
207
227
|
fi
|
package/adapters/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
|