@boyingliu01/xp-gate 0.31.2 → 0.31.3

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/gate-3.sh CHANGED
@@ -20,15 +20,250 @@ if [ "$PROJECT_LANG" = "documentation-only" ]; then
20
20
  GATE_3_REASON="documentation_project"
21
21
 
22
22
  elif [ "$PROJECT_LANG" = "powershell" ]; then
23
- echo "ℹ️ No PowerShell Clean Code / SOLID tool available"
24
- echo "⏭️ SKIPPED - Complexity (no PowerShell tool)"
25
- GATE_3_STATUS="SKIP"
26
- # #537: a PowerShell-primary repo was invisible to this dimension while the
27
- # score implied full coverage — say exactly why nothing ran.
28
- # Consumed by the sourcing parent (pre-commit report JSON), not here.
29
- # shellcheck disable=SC2034
30
- GATE_3_REASON="no_powershell_tool"
31
-
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
+ function norm(p) { return String(p).replace(/\\\\/g, "/").replace(/^\.\//, ""); }
180
+ const seen = new Set();
181
+ const regressions = [];
182
+ const improved = [];
183
+ for (const line of raw.split(/\r?\n/)) {
184
+ const m = line.match(/^(?:OVER|FUNC) (.+):(\d+) (\S+) ccn=(\d+)$/);
185
+ if (!m) continue;
186
+ const key = norm(m[1]) + "::" + m[3];
187
+ const now = Number(m[4]);
188
+ seen.add(key);
189
+ const was = known.has(key) ? known.get(key) : null;
190
+ if (was === null) {
191
+ if (now > threshold) {
192
+ regressions.push(m[1] + ":" + m[2] + " " + m[3] + " new function at ccn=" + now);
193
+ }
194
+ continue;
195
+ }
196
+ if (now > was) {
197
+ regressions.push(m[1] + ":" + m[2] + " " + m[3] + " ccn " + was + " -> " + now);
198
+ } else if (now < was) {
199
+ improved.push(m[1] + ":" + m[2] + " " + m[3] + " ccn " + was + " -> " + now);
200
+ }
201
+ }
202
+ for (const r of regressions) console.log("REGRESSION " + r);
203
+ for (const i of improved) console.log("IMPROVED " + i);
204
+ if (regressions.length > 0) {
205
+ console.log("FAIL " + regressions.length + " function(s) increased cyclomatic complexity vs " + fsPath + ".");
206
+ }
207
+ });
208
+ ' "$PS_BASELINE" "$PS_CC_THRESHOLD" 2>&1 || true)
209
+
210
+ if printf '%s\n' "$PS_CC_VERDICT" | grep -q '^FAIL '; then
211
+ PS_CC_REG=$(printf '%s\n' "$PS_CC_VERDICT" | sed -n 's/^FAIL //p' | tail -1)
212
+ printf '%s\n' "$PS_CC_VERDICT" | grep '^REGRESSION ' | sed 's/^REGRESSION / ❌ /'
213
+ echo "❌ BLOCKED - ${PS_CC_REG}"
214
+ GATE_3_STATUS="BLOCK"
215
+ GATE_3_REASON="ccn_ratchet_regression"
216
+ exit 1
217
+ elif printf '%s\n' "$PS_CC_VERDICT" | grep -q '^ERR '; then
218
+ echo "⚠️ WARN - complexity ratchet baseline unreadable: $(printf '%s\n' "$PS_CC_VERDICT" | sed -n 's/^ERR //p' | head -1)"
219
+ GATE_3_STATUS="WARN"
220
+ GATE_3_REASON="baseline_unreadable"
221
+ else
222
+ PS_CC_IMPROVED=$(printf '%s\n' "$PS_CC_VERDICT" | grep -c '^IMPROVED ' || true)
223
+ printf '%s\n' "$PS_CC_VERDICT" | grep '^IMPROVED ' | sed 's/^IMPROVED / 📉 /'
224
+ echo "✅ PASSED - PowerShell complexity within ratchet (${PS_CC_MEASURED} function(s), ${PS_CC_IMPROVED} improved, threshold ${PS_CC_THRESHOLD})."
225
+ GATE_3_STATUS="PASS"
226
+ GATE_3_DETAIL="powerShell_ratchet improved=${PS_CC_IMPROVED}"
227
+ fi
228
+ fi
229
+ fi
230
+ fi
231
+ else
232
+ # Freshly added files: this arm has no history to compare, so it judges
233
+ # each file's functions against the threshold. A file that is merely
234
+ # being touched keeps its history (that is what the baseline is for);
235
+ # measuring only ADDED files means the arm can never retroactively block
236
+ # a cleanup commit.
237
+ PS_CC_ARGS=()
238
+ for _f in $PS_ADDED; do PS_CC_ARGS[${#PS_CC_ARGS[@]}]="'$_f'"; done
239
+ PS_CC_OUT=$("$PS_CC_PWSH" -NoProfile -NonInteractive -ExecutionPolicy Bypass \
240
+ -Command "& '$PS_CC_SCRIPT' -Path @($(IFS=','; echo "${PS_CC_ARGS[*]}")) -Threshold ${PS_CC_THRESHOLD}" 2>&1 || true)
241
+ PS_CC_MEASURED=$(printf '%s\n' "$PS_CC_OUT" | sed -n 's/^functions_measured=//p' | tail -1)
242
+ PS_CC_OVER=$(printf '%s\n' "$PS_CC_OUT" | sed -n 's/^over_threshold=//p' | tail -1)
243
+ PS_CC_MAX=$(printf '%s\n' "$PS_CC_OUT" | sed -n 's/^max_ccn=//p' | tail -1)
244
+ if [ -z "$PS_CC_MEASURED" ] || [ "${PS_CC_MEASURED:-0}" -eq 0 ]; then
245
+ echo "⏭️ SKIPPED - Complexity (no functions in added PowerShell files)."
246
+ GATE_3_STATUS="SKIP"
247
+ # Consumed by the sourcing parent (pre-commit report JSON), not here.
248
+ # shellcheck disable=SC2034
249
+ GATE_3_REASON="no_functions_found"
250
+ elif [ "${PS_CC_OVER:-0}" -gt 0 ]; then
251
+ echo " Measured ${PS_CC_MEASURED} function(s) via AST; max CCN ${PS_CC_MAX} (threshold ${PS_CC_THRESHOLD})"
252
+ printf '%s\n' "$PS_CC_OUT" | grep '^OVER ' | sed 's/^/ /'
253
+ echo "❌ BLOCKED - ${PS_CC_OVER} newly added function(s) with CCN > ${PS_CC_THRESHOLD}."
254
+ echo "Refactor high-complexity functions, or raise the threshold deliberately."
255
+ GATE_3_STATUS="BLOCK"
256
+ GATE_3_REASON="ccn_over_threshold"
257
+ exit 1
258
+ else
259
+ echo "✅ PASSED - All added PowerShell functions within complexity threshold (${PS_CC_THRESHOLD})."
260
+ echo " Measured ${PS_CC_MEASURED} function(s) via AST; max CCN ${PS_CC_MAX}."
261
+ GATE_3_STATUS="PASS"
262
+ fi
263
+ fi
264
+ fi
265
+ fi
266
+
32
267
  else
33
268
  # S2 (#507): three-level CCN threshold override --
34
269
  # .xp-gate/ccn-threshold (positive integer) > XP_GATE_CCN_THRESHOLD > 5.
package/hooks/gate-3.sh CHANGED
@@ -20,15 +20,250 @@ if [ "$PROJECT_LANG" = "documentation-only" ]; then
20
20
  GATE_3_REASON="documentation_project"
21
21
 
22
22
  elif [ "$PROJECT_LANG" = "powershell" ]; then
23
- echo "ℹ️ No PowerShell Clean Code / SOLID tool available"
24
- echo "⏭️ SKIPPED - Complexity (no PowerShell tool)"
25
- GATE_3_STATUS="SKIP"
26
- # #537: a PowerShell-primary repo was invisible to this dimension while the
27
- # score implied full coverage — say exactly why nothing ran.
28
- # Consumed by the sourcing parent (pre-commit report JSON), not here.
29
- # shellcheck disable=SC2034
30
- GATE_3_REASON="no_powershell_tool"
31
-
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
+ function norm(p) { return String(p).replace(/\\\\/g, "/").replace(/^\.\//, ""); }
180
+ const seen = new Set();
181
+ const regressions = [];
182
+ const improved = [];
183
+ for (const line of raw.split(/\r?\n/)) {
184
+ const m = line.match(/^(?:OVER|FUNC) (.+):(\d+) (\S+) ccn=(\d+)$/);
185
+ if (!m) continue;
186
+ const key = norm(m[1]) + "::" + m[3];
187
+ const now = Number(m[4]);
188
+ seen.add(key);
189
+ const was = known.has(key) ? known.get(key) : null;
190
+ if (was === null) {
191
+ if (now > threshold) {
192
+ regressions.push(m[1] + ":" + m[2] + " " + m[3] + " new function at ccn=" + now);
193
+ }
194
+ continue;
195
+ }
196
+ if (now > was) {
197
+ regressions.push(m[1] + ":" + m[2] + " " + m[3] + " ccn " + was + " -> " + now);
198
+ } else if (now < was) {
199
+ improved.push(m[1] + ":" + m[2] + " " + m[3] + " ccn " + was + " -> " + now);
200
+ }
201
+ }
202
+ for (const r of regressions) console.log("REGRESSION " + r);
203
+ for (const i of improved) console.log("IMPROVED " + i);
204
+ if (regressions.length > 0) {
205
+ console.log("FAIL " + regressions.length + " function(s) increased cyclomatic complexity vs " + fsPath + ".");
206
+ }
207
+ });
208
+ ' "$PS_BASELINE" "$PS_CC_THRESHOLD" 2>&1 || true)
209
+
210
+ if printf '%s\n' "$PS_CC_VERDICT" | grep -q '^FAIL '; then
211
+ PS_CC_REG=$(printf '%s\n' "$PS_CC_VERDICT" | sed -n 's/^FAIL //p' | tail -1)
212
+ printf '%s\n' "$PS_CC_VERDICT" | grep '^REGRESSION ' | sed 's/^REGRESSION / ❌ /'
213
+ echo "❌ BLOCKED - ${PS_CC_REG}"
214
+ GATE_3_STATUS="BLOCK"
215
+ GATE_3_REASON="ccn_ratchet_regression"
216
+ exit 1
217
+ elif printf '%s\n' "$PS_CC_VERDICT" | grep -q '^ERR '; then
218
+ echo "⚠️ WARN - complexity ratchet baseline unreadable: $(printf '%s\n' "$PS_CC_VERDICT" | sed -n 's/^ERR //p' | head -1)"
219
+ GATE_3_STATUS="WARN"
220
+ GATE_3_REASON="baseline_unreadable"
221
+ else
222
+ PS_CC_IMPROVED=$(printf '%s\n' "$PS_CC_VERDICT" | grep -c '^IMPROVED ' || true)
223
+ printf '%s\n' "$PS_CC_VERDICT" | grep '^IMPROVED ' | sed 's/^IMPROVED / 📉 /'
224
+ echo "✅ PASSED - PowerShell complexity within ratchet (${PS_CC_MEASURED} function(s), ${PS_CC_IMPROVED} improved, threshold ${PS_CC_THRESHOLD})."
225
+ GATE_3_STATUS="PASS"
226
+ GATE_3_DETAIL="powerShell_ratchet improved=${PS_CC_IMPROVED}"
227
+ fi
228
+ fi
229
+ fi
230
+ fi
231
+ else
232
+ # Freshly added files: this arm has no history to compare, so it judges
233
+ # each file's functions against the threshold. A file that is merely
234
+ # being touched keeps its history (that is what the baseline is for);
235
+ # measuring only ADDED files means the arm can never retroactively block
236
+ # a cleanup commit.
237
+ PS_CC_ARGS=()
238
+ for _f in $PS_ADDED; do PS_CC_ARGS[${#PS_CC_ARGS[@]}]="'$_f'"; done
239
+ PS_CC_OUT=$("$PS_CC_PWSH" -NoProfile -NonInteractive -ExecutionPolicy Bypass \
240
+ -Command "& '$PS_CC_SCRIPT' -Path @($(IFS=','; echo "${PS_CC_ARGS[*]}")) -Threshold ${PS_CC_THRESHOLD}" 2>&1 || true)
241
+ PS_CC_MEASURED=$(printf '%s\n' "$PS_CC_OUT" | sed -n 's/^functions_measured=//p' | tail -1)
242
+ PS_CC_OVER=$(printf '%s\n' "$PS_CC_OUT" | sed -n 's/^over_threshold=//p' | tail -1)
243
+ PS_CC_MAX=$(printf '%s\n' "$PS_CC_OUT" | sed -n 's/^max_ccn=//p' | tail -1)
244
+ if [ -z "$PS_CC_MEASURED" ] || [ "${PS_CC_MEASURED:-0}" -eq 0 ]; then
245
+ echo "⏭️ SKIPPED - Complexity (no functions in added PowerShell files)."
246
+ GATE_3_STATUS="SKIP"
247
+ # Consumed by the sourcing parent (pre-commit report JSON), not here.
248
+ # shellcheck disable=SC2034
249
+ GATE_3_REASON="no_functions_found"
250
+ elif [ "${PS_CC_OVER:-0}" -gt 0 ]; then
251
+ echo " Measured ${PS_CC_MEASURED} function(s) via AST; max CCN ${PS_CC_MAX} (threshold ${PS_CC_THRESHOLD})"
252
+ printf '%s\n' "$PS_CC_OUT" | grep '^OVER ' | sed 's/^/ /'
253
+ echo "❌ BLOCKED - ${PS_CC_OVER} newly added function(s) with CCN > ${PS_CC_THRESHOLD}."
254
+ echo "Refactor high-complexity functions, or raise the threshold deliberately."
255
+ GATE_3_STATUS="BLOCK"
256
+ GATE_3_REASON="ccn_over_threshold"
257
+ exit 1
258
+ else
259
+ echo "✅ PASSED - All added PowerShell functions within complexity threshold (${PS_CC_THRESHOLD})."
260
+ echo " Measured ${PS_CC_MEASURED} function(s) via AST; max CCN ${PS_CC_MAX}."
261
+ GATE_3_STATUS="PASS"
262
+ fi
263
+ fi
264
+ fi
265
+ fi
266
+
32
267
  else
33
268
  # S2 (#507): three-level CCN threshold override --
34
269
  # .xp-gate/ccn-threshold (positive integer) > XP_GATE_CCN_THRESHOLD > 5.
@@ -0,0 +1,186 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Measure cyclomatic complexity of PowerShell functions via the AST.
4
+
5
+ .DESCRIPTION
6
+ lizard cannot parse PowerShell, so a PowerShell-primary repository had a
7
+ permanently unmeasured complexity dimension (#537). The platform, however,
8
+ CAN answer the question: PowerShell's own AST yields an exact decision-point
9
+ count per function, which is the same quantity lizard measures for other
10
+ languages.
11
+
12
+ This script is the measurement half; gate-3.sh is the verdict half. Keeping
13
+ them apart means the gate does not have to know a project's refactor history
14
+ and the script does not have to know about baselines.
15
+
16
+ Output is line-oriented and stable, because gate-3.sh parses it:
17
+
18
+ functions_measured=<n> total functions inspected
19
+ max_ccn=<n> highest CCN seen
20
+ over_threshold=<n> functions with CCN > -Threshold
21
+ OVER <file>:<line> <name> ccn=<n> one per over-threshold function
22
+ FUNC <file>:<line> <name> ccn=<n> one per function when -List is set
23
+
24
+ .PARAMETER Path
25
+ Files or directories to analyse. Directories are walked for *.ps1/*.psm1.
26
+
27
+ .PARAMETER Threshold
28
+ Reporting threshold. Functions above it are emitted as OVER lines. This does
29
+ NOT filter FUNC lines, which is what makes a ratchet able to compare against
30
+ a baseline collected at a different threshold -- see #598.
31
+
32
+ .PARAMETER List
33
+ Emit a FUNC line for every function, not just the ones over the threshold.
34
+ A ratchet needs the full population: comparing only OVER lines against a
35
+ baseline that also stores only over-threshold entries is how a brand-new
36
+ well-factored function (CCN 2-4) got reported as a "regression".
37
+
38
+ .PARAMETER Strict
39
+ Treat a parse failure as a hard error (exit 3) instead of skipping the file.
40
+
41
+ .EXAMPLE
42
+ ./Measure-PowerShellComplexity.ps1 -Path ./src -Threshold 5
43
+
44
+ .EXAMPLE
45
+ ./Measure-PowerShellComplexity.ps1 -Path ./src -Threshold 5 -List
46
+
47
+ .NOTES
48
+ Exit codes:
49
+ 0 measured (findings are reported, not signalled by exit code)
50
+ 2 no analyzable files
51
+ 3 parse/tooling failure (fail closed)
52
+ #>
53
+ [CmdletBinding()]
54
+ param(
55
+ [Parameter(Mandatory = $false)]
56
+ [string[]] $Path = @('.'),
57
+
58
+ [Parameter(Mandatory = $false)]
59
+ [ValidateRange(1, 1000)]
60
+ [int] $Threshold = 5,
61
+
62
+ [Parameter(Mandatory = $false)]
63
+ [switch] $List,
64
+
65
+ [Parameter(Mandatory = $false)]
66
+ [switch] $Strict
67
+ )
68
+
69
+ Set-StrictMode -Version Latest
70
+ $ErrorActionPreference = 'Stop'
71
+
72
+ # --- Collect files -----------------------------------------------------------
73
+
74
+ $files = New-Object System.Collections.Generic.List[string]
75
+ $missing = 0
76
+
77
+ foreach ($p in $Path) {
78
+ if (-not (Test-Path -LiteralPath $p)) { $missing++; continue }
79
+ $item = Get-Item -LiteralPath $p
80
+ if ($item.PSIsContainer) {
81
+ Get-ChildItem -LiteralPath $item.FullName -Recurse -File -Include '*.ps1', '*.psm1' |
82
+ ForEach-Object { $files.Add($_.FullName) }
83
+ }
84
+ elseif ($item.Extension -in @('.ps1', '.psm1')) {
85
+ $files.Add($item.FullName)
86
+ }
87
+ }
88
+
89
+ if ($files.Count -eq 0) {
90
+ if ($Strict -and $missing -gt 0) {
91
+ [Console]::Error.WriteLine("ERR no analyzable PowerShell files ($missing path(s) did not exist)")
92
+ exit 3
93
+ }
94
+ Write-Output 'functions_measured=0'
95
+ Write-Output 'max_ccn=0'
96
+ Write-Output 'over_threshold=0'
97
+ exit 2
98
+ }
99
+
100
+ # --- Count decision points ---------------------------------------------------
101
+ #
102
+ # CCN = 1 + number of decision points, which is the definition lizard uses.
103
+ # Decision points in PowerShell: if / elseif, the loop keywords, catch, the
104
+ # ternary and null-coalescing operators, and each -and/-or in a condition.
105
+ # Declaring a function is not itself a decision, so every function starts at 1.
106
+
107
+ function Get-DecisionCount {
108
+ param([System.Management.Automation.Language.Ast] $AstBody)
109
+
110
+ $count = 0
111
+
112
+ $count += @($AstBody.FindAll({ param($n) $n -is [System.Management.Automation.Language.IfStatementAst] }, $true)).Count
113
+ $count += @($AstBody.FindAll({ param($n) $n -is [System.Management.Automation.Language.LoopStatementAst] }, $true)).Count
114
+ $count += @($AstBody.FindAll({ param($n) $n -is [System.Management.Automation.Language.TrapStatementAst] }, $true)).Count
115
+ $count += @($AstBody.FindAll({ param($n) $n -is [System.Management.Automation.Language.TernaryExpressionAst] }, $true)).Count
116
+
117
+ # Binary expressions whose operator short-circuits count as decisions, which
118
+ # is what makes `if ($a -and $b -or $c)` score higher than `if ($a)`.
119
+ foreach ($bin in $AstBody.FindAll({ param($n) $n -is [System.Management.Automation.Language.BinaryExpressionAst] }, $true)) {
120
+ $op = $null
121
+ try { $op = $bin.Operator } catch { continue }
122
+ if ($op -eq 'And' -or $op -eq 'Or') { $count++ }
123
+ }
124
+
125
+ # catch blocks: FindAll over class-based ASTs is not available for them, so
126
+ # walk TryStatementAst nodes and count their CatchClauses.
127
+ foreach ($try in $AstBody.FindAll({ param($n) $n -is [System.Management.Automation.Language.TryStatementAst] }, $true)) {
128
+ $count += @($try.CatchClauses).Count
129
+ }
130
+
131
+ return $count
132
+ }
133
+
134
+ $measured = 0
135
+ $maxCcn = 0
136
+ $over = 0
137
+ $parseErrors = New-Object System.Collections.Generic.List[string]
138
+
139
+ foreach ($file in $files) {
140
+ $tokens = $null
141
+ $errors = $null
142
+ $ast = $null
143
+ try {
144
+ $ast = [System.Management.Automation.Language.Parser]::ParseFile($file, [ref]$tokens, [ref]$errors)
145
+ }
146
+ catch {
147
+ $parseErrors.Add("$file : $($_.Exception.Message)")
148
+ continue
149
+ }
150
+
151
+ if ($errors -and $errors.Count -gt 0) {
152
+ $first = ($errors | Select-Object -First 1)
153
+ $parseErrors.Add("$file : $($first.Message)")
154
+ continue
155
+ }
156
+
157
+ $funcs = $ast.FindAll({ param($n) $n -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true)
158
+ foreach ($f in $funcs) {
159
+ $ccn = 1 + (Get-DecisionCount -AstBody $f.Body)
160
+ $measured++
161
+ if ($ccn -gt $maxCcn) { $maxCcn = $ccn }
162
+
163
+ if ($ccn -gt $Threshold) { $over++ }
164
+
165
+ if ($List -or $ccn -gt $Threshold) {
166
+ $line = $f.Extent.StartLineNumber
167
+ $kind = if ($ccn -gt $Threshold) { 'OVER' } elseif ($List) { 'FUNC' } else { $null }
168
+ if ($kind) {
169
+ Write-Output ('{0} {1}:{2} {3} ccn={4}' -f $kind, $file, $line, $f.Name, $ccn)
170
+ }
171
+ }
172
+ }
173
+ }
174
+
175
+ if ($parseErrors.Count -gt 0 -and $Strict) {
176
+ foreach ($pe in $parseErrors) { [Console]::Error.WriteLine("ERR parse: $pe") }
177
+ exit 3
178
+ }
179
+ elseif ($parseErrors.Count -gt 0) {
180
+ foreach ($pe in $parseErrors) { [Console]::Error.WriteLine("WARN parse skipped: $pe") }
181
+ }
182
+
183
+ Write-Output "functions_measured=$measured"
184
+ Write-Output "max_ccn=$maxCcn"
185
+ Write-Output "over_threshold=$over"
186
+ exit 0
package/hooks/pre-commit CHANGED
@@ -2130,6 +2130,13 @@ source "$GATE_DIR/gate-4.sh"
2130
2130
  2>&1 echo ""
2131
2131
  2>&1 echo "→ Gate 5: Tests & coverage..."
2132
2132
  GATE_5_START=$(gate_start_ms)
2133
+ # #554 follow-up: explicit "nothing decided yet" sentinel. A branch inside the
2134
+ # else-block may record its own verdict (the no-code early skip records SKIP);
2135
+ # the epilogue must not overwrite it with PASS. Using "" rather than leaving the
2136
+ # variable unset keeps the epilogue's decision readable, and keeps every render
2137
+ # site's own default (`${GATE_5_STATUS:-SKIP}`) honest for the paths that never
2138
+ # assign at all.
2139
+ GATE_5_STATUS=""
2133
2140
 
2134
2141
  if [ "$PROJECT_LANG" = "documentation-only" ] || [ "$DOCS_ONLY_COMMIT" = true ]; then
2135
2142
  echo "⏭️ SKIPPED - Tests & coverage (documentation-only commit — no code changed to test)."
@@ -2491,10 +2498,19 @@ else
2491
2498
  CODE_EXTENSIONS="\.(ts|tsx|js|jsx|py|go|java|kt|kts|cpp|cc|cxx|c|swift|m|mm|dart|sh|ps1)$"
2492
2499
  CHANGED_CODE_FILES=$(echo "$CHANGED_FILES" | grep -E "$CODE_EXTENSIONS" || true)
2493
2500
  if [ -z "$CHANGED_CODE_FILES" ]; then
2494
- echo "✅ PASSED - No code files changed (only docs/config/VERSION), skipping test run."
2501
+ # #554: this branch DID stand down deliberately, but "PASS" is a claim that
2502
+ # a check ran and found nothing. Nothing ran here: no test executed, no
2503
+ # coverage number exists. Recording PASS inflated the pass rate and made an
2504
+ # unmeasured gate indistinguishable from a measured clean one -- the same
2505
+ # defect #554 fixed on the docs-only branch, on the branch that fix did not
2506
+ # cover (measured on the v0.31.2.0 release commit: journal said
2507
+ # gate5_tests.status=PASS while the summary printed "coverage: N/A%").
2508
+ echo "⏭️ SKIPPED - No code files changed (only docs/config/VERSION) — test run not needed."
2495
2509
  echo " Changed files: $(echo "$CHANGED_FILES" | tr '\n' ' ')"
2496
2510
  TESTS_EXIT_CODE=0
2497
2511
  TESTS_SKIPPED=true
2512
+ GATE_5_STATUS="SKIP"
2513
+ GATE_5_DETAIL="no_code_files_changed"
2498
2514
  GATE_5_END=$(gate_start_ms)
2499
2515
  GATE_5_ELAPSED=$((GATE_5_END - GATE_5_START))
2500
2516
  echo " ⏱️ Gate 5: ${GATE_5_ELAPSED}ms (skipped — no code changes)"
@@ -3312,7 +3328,20 @@ else
3312
3328
  # "Tests + Coverage: PASS (coverage: N/A)" for a commit where no test ran
3313
3329
  # (#554) -- a SKIP promoted to PASS inflates the pass rate and hides that the
3314
3330
  # gate never executed.
3315
- GATE_5_STATUS="PASS"
3331
+ #
3332
+ # #554 follow-up: the same promotion was still happening ONE LEVEL IN. The
3333
+ # early-skip branch above (no source extension among the changed files, e.g. a
3334
+ # version bump) sets GATE_5_STATUS="SKIP", and this unguarded assignment
3335
+ # overwrote it on the way out of the same else-block. A branch that records
3336
+ # its own verdict must not be re-decided by the block epilogue: default to
3337
+ # PASS only when the sentinel is still untouched.
3338
+ #
3339
+ # Not written as a `:-PASS` expansion: AC-599-03 forbids a PASS default in
3340
+ # any `GATE_N_STATUS:-DEFAULT` expansion, because that is how an unassigned
3341
+ # gate came to render as "passed". An explicit comparison keeps both rules.
3342
+ if [ -z "$GATE_5_STATUS" ]; then
3343
+ GATE_5_STATUS="PASS"
3344
+ fi
3316
3345
  fi
3317
3346
  record_gate_audit "gate-5" "tests-coverage" "$GATE_5_STATUS" "0" "$GATE_5_START" "${GATE_5_DETAIL:-}"
3318
3347
 
@@ -4237,6 +4266,7 @@ generate_quality_report() {
4237
4266
  "gate5_tests": {
4238
4267
  "name": "Tests + Coverage",
4239
4268
  "status": "${GATE_5_STATUS:-SKIP}",
4269
+ "reason": "${GATE_5_DETAIL:-}",
4240
4270
  "thresholds": { "lines": ${COVERAGE_THRESHOLD}, "functions": null, "branches": null, "statements": null },
4241
4271
  "threshold_source": "${COVERAGE_THRESHOLD_SOURCE}",
4242
4272
  "actual": { "coverage": "${COV_PCT}" },
@@ -2,6 +2,6 @@
2
2
  "verdict": "PASS",
3
3
  "license_type": "MIT",
4
4
  "sha256": "0e7ac423bf2c6e223b7c5b156f8cf72da49d748e56a1641402c31f22ad07dbb5",
5
- "checked_at": "2026-10-25T15:15:20.118Z",
5
+ "checked_at": "2026-10-26T05:44:50.695Z",
6
6
  "source": "network"
7
7
  }
@@ -1,9 +1,9 @@
1
1
  # SRC/MOCK-POLICY KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-25
4
- **Commit:** 1ab53eff
3
+ **Generated:** 2026-10-26
4
+ **Commit:** 7a6d9971
5
5
  **Branch:** main
6
- **Version:** 0.31.2.0
6
+ **Version:** 0.31.3.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Mock layering policy enforcement — Gate M3 of pre-push hook. Ensures integration tests use real implementations for internal dependencies, mock external dependencies, and annotate pending mocks with removal plans. Combines project scope scanning, mock decision engine, and per-file validation into a single pipeline.
@@ -1,9 +1,9 @@
1
1
  # SRC/MUTATION KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-25
4
- **Commit:** 1ab53eff
3
+ **Generated:** 2026-10-26
4
+ **Commit:** 7a6d9971
5
5
  **Branch:** main
6
- **Version:** 0.31.2.0
6
+ **Version:** 0.31.3.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **Gate M** (incremental mutation testing) + **Gate M2** helpers (test-layer detection used by `src/mock-policy/`). Pre-push quality gate. TypeScript-only; uses Stryker.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyingliu01/xp-gate",
3
- "version": "0.31.2",
3
+ "version": "0.31.3",
4
4
  "description": "AI-driven development workflow: 6 quality gates + Delphi review + Sprint Flow",
5
5
  "bin": {
6
6
  "xp-gate": "bin/xp-gate.js"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xp-gate",
3
- "version": "0.31.2",
3
+ "version": "0.31.3",
4
4
  "displayName": "XP-Gate",
5
5
  "description": "Extreme Programming quality gates + AI workflow skills for Claude Code. Includes 12 pre-commit gates (Gate 0-11), 8 pre-push gates, Sprint Flow (6 phases), and Delphi multi-expert review (>=90% consensus).",
6
6
  "author": {
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-25
4
- **Commit:** 1ab53eff
3
+ **Generated:** 2026-10-26
4
+ **Commit:** 7a6d9971
5
5
  **Branch:** main
6
- **Version:** 0.31.2.0
6
+ **Version:** 0.31.3.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-25
4
- **Commit:** 1ab53eff
3
+ **Generated:** 2026-10-26
4
+ **Commit:** 7a6d9971
5
5
  **Branch:** main
6
- **Version:** 0.31.2.0
6
+ **Version:** 0.31.3.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-25
4
- **Commit:** 1ab53eff
3
+ **Generated:** 2026-10-26
4
+ **Commit:** 7a6d9971
5
5
  **Branch:** main
6
- **Version:** 0.31.2.0
6
+ **Version:** 0.31.3.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyingliu01/dsh-plugin-xp-gate",
3
- "version": "0.31.2",
3
+ "version": "0.31.3",
4
4
  "description": "XP-Gate deterministic quality gates + AI workflow skills as a DeepSeek Harness (DSH) native plugin: gate-check / gate-principles / gate-arch tools + 12 SKILL.md skills",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-25
4
- **Commit:** 1ab53eff
3
+ **Generated:** 2026-10-26
4
+ **Commit:** 7a6d9971
5
5
  **Branch:** main
6
- **Version:** 0.31.2.0
6
+ **Version:** 0.31.3.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-25
4
- **Commit:** 1ab53eff
3
+ **Generated:** 2026-10-26
4
+ **Commit:** 7a6d9971
5
5
  **Branch:** main
6
- **Version:** 0.31.2.0
6
+ **Version:** 0.31.3.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-25
4
- **Commit:** 1ab53eff
3
+ **Generated:** 2026-10-26
4
+ **Commit:** 7a6d9971
5
5
  **Branch:** main
6
- **Version:** 0.31.2.0
6
+ **Version:** 0.31.3.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyingliu01/opencode-plugin",
3
- "version": "0.31.2",
3
+ "version": "0.31.3",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "description": "XP-Gate quality gates + AI workflow skills + Sprint Flow TUI sidebar for OpenCode",
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-25
4
- **Commit:** 1ab53eff
3
+ **Generated:** 2026-10-26
4
+ **Commit:** 7a6d9971
5
5
  **Branch:** main
6
- **Version:** 0.31.2.0
6
+ **Version:** 0.31.3.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-25
4
- **Commit:** 1ab53eff
3
+ **Generated:** 2026-10-26
4
+ **Commit:** 7a6d9971
5
5
  **Branch:** main
6
- **Version:** 0.31.2.0
6
+ **Version:** 0.31.3.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-25
4
- **Commit:** 1ab53eff
3
+ **Generated:** 2026-10-26
4
+ **Commit:** 7a6d9971
5
5
  **Branch:** main
6
- **Version:** 0.31.2.0
6
+ **Version:** 0.31.3.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xp-gate",
3
- "version": "0.31.2",
3
+ "version": "0.31.3",
4
4
  "displayName": "XP-Gate",
5
5
  "description": "Extreme Programming quality gates + AI workflow skills for Qoder. Includes 12 pre-commit gates (Gate 0-11), 8 pre-push gates, Sprint Flow (6 phases), and Delphi multi-expert review (>=90% consensus).",
6
6
  "author": {
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-25
4
- **Commit:** 1ab53eff
3
+ **Generated:** 2026-10-26
4
+ **Commit:** 7a6d9971
5
5
  **Branch:** main
6
- **Version:** 0.31.2.0
6
+ **Version:** 0.31.3.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-25
4
- **Commit:** 1ab53eff
3
+ **Generated:** 2026-10-26
4
+ **Commit:** 7a6d9971
5
5
  **Branch:** main
6
- **Version:** 0.31.2.0
6
+ **Version:** 0.31.3.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-25
4
- **Commit:** 1ab53eff
3
+ **Generated:** 2026-10-26
4
+ **Commit:** 7a6d9971
5
5
  **Branch:** main
6
- **Version:** 0.31.2.0
6
+ **Version:** 0.31.3.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -1,9 +1,9 @@
1
1
  # PRINCIPLES CHECKER MODULE
2
2
 
3
- **Generated:** 2026-10-25
4
- **Commit:** 1ab53eff
3
+ **Generated:** 2026-10-26
4
+ **Commit:** 7a6d9971
5
5
  **Branch:** main
6
- **Version:** 0.31.2.0
6
+ **Version:** 0.31.3.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Clean Code & SOLID principles checker — **Gate 4** of pre-commit. 14 rules × 9 language adapters, SARIF 2.1.0 output. Houses the **Boy Scout Rule** enforcement engine (Gate 6) and warning-baseline storage.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-25
4
- **Commit:** 1ab53eff
3
+ **Generated:** 2026-10-26
4
+ **Commit:** 7a6d9971
5
5
  **Branch:** main
6
- **Version:** 0.31.2.0
6
+ **Version:** 0.31.3.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-25
4
- **Commit:** 1ab53eff
3
+ **Generated:** 2026-10-26
4
+ **Commit:** 7a6d9971
5
5
  **Branch:** main
6
- **Version:** 0.31.2.0
6
+ **Version:** 0.31.3.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-25
4
- **Commit:** 1ab53eff
3
+ **Generated:** 2026-10-26
4
+ **Commit:** 7a6d9971
5
5
  **Branch:** main
6
- **Version:** 0.31.2.0
6
+ **Version:** 0.31.3.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.