@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 +244 -9
- package/hooks/gate-3.sh +244 -9
- package/hooks/lib/Measure-PowerShellComplexity.ps1 +186 -0
- package/hooks/pre-commit +32 -2
- package/lib/mattpocock-license-verdict.json +1 -1
- package/mock-policy/AGENTS.md +3 -3
- package/mutation/AGENTS.md +3 -3
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
- package/principles/AGENTS.md +3 -3
- package/skills/delphi-review/AGENTS.md +3 -3
- package/skills/sprint-flow/AGENTS.md +3 -3
- package/skills/test-specification-alignment/AGENTS.md +3 -3
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}" },
|
package/mock-policy/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SRC/MOCK-POLICY KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-26
|
|
4
|
+
**Commit:** 7a6d9971
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.31.
|
|
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.
|
package/mutation/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SRC/MUTATION KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-26
|
|
4
|
+
**Commit:** 7a6d9971
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.31.
|
|
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": "xp-gate",
|
|
3
|
-
"version": "0.31.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-26
|
|
4
|
+
**Commit:** 7a6d9971
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.31.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-26
|
|
4
|
+
**Commit:** 7a6d9971
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.31.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-26
|
|
4
|
+
**Commit:** 7a6d9971
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.31.
|
|
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.
|
package/plugins/dsh/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boyingliu01/dsh-plugin-xp-gate",
|
|
3
|
-
"version": "0.31.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-26
|
|
4
|
+
**Commit:** 7a6d9971
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.31.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-26
|
|
4
|
+
**Commit:** 7a6d9971
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.31.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-26
|
|
4
|
+
**Commit:** 7a6d9971
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.31.
|
|
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
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-26
|
|
4
|
+
**Commit:** 7a6d9971
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.31.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-26
|
|
4
|
+
**Commit:** 7a6d9971
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.31.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-26
|
|
4
|
+
**Commit:** 7a6d9971
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.31.
|
|
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.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-26
|
|
4
|
+
**Commit:** 7a6d9971
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.31.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-26
|
|
4
|
+
**Commit:** 7a6d9971
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.31.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-26
|
|
4
|
+
**Commit:** 7a6d9971
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.31.
|
|
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.
|
package/principles/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# PRINCIPLES CHECKER MODULE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-26
|
|
4
|
+
**Commit:** 7a6d9971
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.31.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-26
|
|
4
|
+
**Commit:** 7a6d9971
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.31.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-26
|
|
4
|
+
**Commit:** 7a6d9971
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.31.
|
|
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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-26
|
|
4
|
+
**Commit:** 7a6d9971
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.31.
|
|
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.
|