@boyingliu01/xp-gate 0.27.6 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapter-common.sh +0 -0
- package/adapters/cpp.sh +0 -0
- package/adapters/dart.sh +0 -0
- package/adapters/flutter.sh +0 -0
- package/adapters/gate-9.sh +13 -2
- package/adapters/go.sh +0 -0
- package/adapters/iac.sh +0 -0
- package/adapters/java.sh +0 -0
- package/adapters/kotlin.sh +0 -0
- package/adapters/objectivec.sh +0 -0
- package/adapters/plugins/book299-20132-python/scripts/install-gradle.sh +0 -0
- package/adapters/plugins/book299-20132-python/scripts/install-maven.sh +0 -0
- package/adapters/plugins/book299-4081-c/scripts/install-gradle.sh +0 -0
- package/adapters/plugins/book299-4081-c/scripts/install-maven.sh +0 -0
- package/adapters/plugins/book299-4083-javascriptes5/scripts/install-gradle.sh +0 -0
- package/adapters/plugins/book299-4083-javascriptes5/scripts/install-maven.sh +0 -0
- package/adapters/powershell.sh +0 -0
- package/adapters/python.sh +0 -0
- package/adapters/shell.sh +0 -0
- package/adapters/swift.sh +0 -0
- package/adapters/typescript.sh +0 -0
- package/bin/xp-gate.js +0 -0
- package/build-integrity/gate-10.ts +290 -263
- package/gate-10.sh +25 -6
- package/gate-12-file-hygiene.sh +35 -1
- package/gate-3.sh +78 -2
- package/gate-4.sh +0 -0
- package/gate-7.sh +0 -0
- package/gate-8.sh +0 -0
- package/gate-9.sh +13 -2
- package/hooks/adapter-common.sh +0 -0
- package/hooks/gate-10.sh +25 -6
- package/hooks/gate-12-file-hygiene.sh +35 -1
- package/hooks/gate-3.sh +78 -2
- package/hooks/gate-4.sh +0 -0
- package/hooks/gate-7.sh +0 -0
- package/hooks/gate-8.sh +0 -0
- package/hooks/gate-9.sh +13 -2
- package/hooks/lib/audit-sanitize.sh +51 -0
- package/hooks/lib/ccn-ratchet.sh +183 -0
- package/hooks/lib/docs-only.sh +49 -0
- package/hooks/post-merge +0 -0
- package/hooks/pre-commit +74 -15
- package/hooks/pre-push +19 -4
- package/hooks/sprint-gate.sh +0 -0
- package/lib/__tests__/sprint-audit.test.js +207 -207
- package/lib/gate-audit.ts +9 -4
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/retro.js +27 -6
- package/lib/sprint-audit.js +235 -235
- 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/bin/delphi-review-guard.sh +0 -0
- package/plugins/claude-code/bin/xp-gate-check +0 -0
- 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/bin/sprint-flow-guard.sh +0 -0
- package/plugins/qoder/bin/xp-gate-check +0 -0
- 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/principles/__tests__/many-exports-reexport.test.ts +40 -0
- package/principles/adapters/typescript.ts +61 -30
- package/principles/boy-scout.ts +22 -17
- package/scripts/delphi-external-review.cjs +195 -132
- 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/sprint-gate.sh +0 -0
- package/plugins/dsh/lib/command.d.ts +0 -32
- package/plugins/dsh/lib/command.d.ts.map +0 -1
- package/plugins/dsh/lib/command.js +0 -81
- package/plugins/dsh/lib/delphi-config.d.ts +0 -75
- package/plugins/dsh/lib/delphi-config.d.ts.map +0 -1
- package/plugins/dsh/lib/delphi-config.js +0 -183
- package/plugins/dsh/lib/delphi-run.d.ts +0 -24
- package/plugins/dsh/lib/delphi-run.d.ts.map +0 -1
- package/plugins/dsh/lib/delphi-run.js +0 -92
- package/plugins/dsh/lib/delphi.d.ts +0 -88
- package/plugins/dsh/lib/delphi.d.ts.map +0 -1
- package/plugins/dsh/lib/delphi.js +0 -161
- package/plugins/dsh/lib/dialect.d.ts +0 -24
- package/plugins/dsh/lib/dialect.d.ts.map +0 -1
- package/plugins/dsh/lib/dialect.js +0 -36
- package/plugins/dsh/lib/gate-runner.d.ts +0 -18
- package/plugins/dsh/lib/gate-runner.d.ts.map +0 -1
- package/plugins/dsh/lib/gate-runner.js +0 -46
- package/plugins/dsh/lib/index.d.ts +0 -5
- package/plugins/dsh/lib/index.d.ts.map +0 -1
- package/plugins/dsh/lib/index.js +0 -162
- package/plugins/dsh/node_modules/.vite/vitest/results.json +0 -1
package/gate-10.sh
CHANGED
|
@@ -53,12 +53,31 @@ else
|
|
|
53
53
|
# lines to `read`; bash never rescans the result for separators. Its correct
|
|
54
54
|
# reading rests on one property of the producer above, not of the loop:
|
|
55
55
|
# `git diff --cached --name-only` terminates paths with LF and quotes control
|
|
56
|
-
# characters (a CR in a name arrives as the four bytes \215, never as a bare
|
|
57
|
-
# CR), so one line is exactly one path. Asserted for spaces and a leading '-'
|
|
58
56
|
# by AC-490-04.
|
|
57
|
+
#
|
|
58
|
+
# #543: semgrep 1.171 ignores .semgrepignore ENTIRELY when files are passed
|
|
59
|
+
# as explicit targets (measured: file target -> an ignored finding survives
|
|
60
|
+
# every suppression form — ignore patterns, --exclude, --exclude-rule,
|
|
61
|
+
# --project-root; directory target + --include -> the ignore file is honored,
|
|
62
|
+
# proven fail-closed by removing it and watching the findings return). The
|
|
63
|
+
# gate therefore passes each staged file's DIRECTORY as a target plus one
|
|
64
|
+
# --include per staged basename. Coverage stays exact for the staged set
|
|
65
|
+
# (verified: scanned == staged); the only widening is same-basename siblings
|
|
66
|
+
# outside the staged set, which scan too — scanning MORE is the safe side.
|
|
59
67
|
SEMGREP_ARGS=()
|
|
68
|
+
SEMGREP_FILE_COUNT=0
|
|
69
|
+
SEMGREP_DIRS_SEEN=$'\n'
|
|
60
70
|
while IFS= read -r _semgrep_file; do
|
|
61
|
-
[ -n "$_semgrep_file" ]
|
|
71
|
+
[ -n "$_semgrep_file" ] || continue
|
|
72
|
+
SEMGREP_FILE_COUNT=$((SEMGREP_FILE_COUNT + 1))
|
|
73
|
+
SEMGREP_ARGS+=("--include=${_semgrep_file##*/}")
|
|
74
|
+
_semgrep_dir="./${_semgrep_file%/*}"
|
|
75
|
+
# A repo-root file has no directory component ("./x.ts" strips to "./").
|
|
76
|
+
[ "$_semgrep_dir" = "./" ] && _semgrep_dir="."
|
|
77
|
+
case "$SEMGREP_DIRS_SEEN" in
|
|
78
|
+
*"$_semgrep_dir"*) : ;;
|
|
79
|
+
*) SEMGREP_ARGS+=("$_semgrep_dir"); SEMGREP_DIRS_SEEN="$SEMGREP_DIRS_SEEN$_semgrep_dir"$'\n' ;;
|
|
80
|
+
esac
|
|
62
81
|
done <<EOF
|
|
63
82
|
$SEMGREP_FILES
|
|
64
83
|
EOF
|
|
@@ -173,12 +192,12 @@ console.log("");});
|
|
|
173
192
|
echo "$FINDING_DETAILS"
|
|
174
193
|
echo " Run 'semgrep scan --config=p/security-audit' to review all findings."
|
|
175
194
|
GATE_10_STATUS="FAIL"
|
|
176
|
-
GATE_10_SCANNED_FILES=$
|
|
195
|
+
GATE_10_SCANNED_FILES=$SEMGREP_FILE_COUNT
|
|
177
196
|
exit 1
|
|
178
197
|
else
|
|
179
|
-
GATE_10_SCANNED_FILES=$
|
|
198
|
+
GATE_10_SCANNED_FILES=$SEMGREP_FILE_COUNT
|
|
180
199
|
echo ""
|
|
181
|
-
echo " ✅ PASSED - No blocking vulnerabilities (files scanned: $
|
|
200
|
+
echo " ✅ PASSED - No blocking vulnerabilities (files scanned: $SEMGREP_FILE_COUNT)"
|
|
182
201
|
if [ "$V_WARNINGS" -gt 0 ]; then
|
|
183
202
|
echo " ⚠️ ${V_WARNINGS} non-blocking findings (warnings only)"
|
|
184
203
|
echo "$FINDING_DETAILS"
|
package/gate-12-file-hygiene.sh
CHANGED
|
@@ -25,11 +25,20 @@ if [ -z "$STAGED_FILES" ]; then
|
|
|
25
25
|
return 0 2>/dev/null || true
|
|
26
26
|
fi
|
|
27
27
|
|
|
28
|
+
# REQ-F2: a docs-only commit cannot introduce code regressions; conflict
|
|
29
|
+
# markers and YAML/JSON syntax are still checked (diff-scoped below).
|
|
30
|
+
if [ "${DOCS_ONLY_COMMIT:-false}" = true ]; then
|
|
31
|
+
echo " ⏭️ SKIPPED - File hygiene heavy checks (documentation-only commit; diff-scoped checks still run)."
|
|
32
|
+
GATE_12_STATUS="SKIP"
|
|
33
|
+
GATE_12_DOCS_ONLY=true
|
|
34
|
+
fi
|
|
35
|
+
|
|
28
36
|
HYGIENE_ISSUES=0
|
|
29
37
|
HYGIENE_FILES=""
|
|
30
38
|
|
|
31
39
|
# --- Check 1: Trailing whitespace ---
|
|
32
40
|
TRAILING_WS_FILES=""
|
|
41
|
+
if [ "${GATE_12_DOCS_ONLY:-false}" != true ]; then
|
|
33
42
|
while IFS= read -r file; do
|
|
34
43
|
[ -z "$file" ] && continue
|
|
35
44
|
# Skip binary files by checking if file contains null bytes
|
|
@@ -43,6 +52,7 @@ while IFS= read -r file; do
|
|
|
43
52
|
done <<EOF
|
|
44
53
|
$STAGED_FILES
|
|
45
54
|
EOF
|
|
55
|
+
fi
|
|
46
56
|
|
|
47
57
|
if [ -n "$TRAILING_WS_FILES" ]; then
|
|
48
58
|
echo " ⚠️ Trailing whitespace detected in:"
|
|
@@ -55,6 +65,7 @@ fi
|
|
|
55
65
|
|
|
56
66
|
# --- Check 2: Missing EOF newline ---
|
|
57
67
|
NO_EOF_NEWLINE_FILES=""
|
|
68
|
+
if [ "${GATE_12_DOCS_ONLY:-false}" != true ]; then
|
|
58
69
|
while IFS= read -r file; do
|
|
59
70
|
[ -z "$file" ] && continue
|
|
60
71
|
# Skip binary files
|
|
@@ -72,6 +83,7 @@ while IFS= read -r file; do
|
|
|
72
83
|
done <<EOF
|
|
73
84
|
$STAGED_FILES
|
|
74
85
|
EOF
|
|
86
|
+
fi
|
|
75
87
|
|
|
76
88
|
if [ -n "$NO_EOF_NEWLINE_FILES" ]; then
|
|
77
89
|
echo " ⚠️ Missing EOF newline in:"
|
|
@@ -83,7 +95,16 @@ if [ -n "$NO_EOF_NEWLINE_FILES" ]; then
|
|
|
83
95
|
fi
|
|
84
96
|
|
|
85
97
|
# --- Check 3: Merge conflict markers ---
|
|
98
|
+
# Runs in BOTH modes. Docs-only commits read the staged DIFF (hunk cost)
|
|
99
|
+
# instead of every full blob — a marker line must be ADDED to matter, so
|
|
100
|
+
# '^+' minus '+++' headers is the precise scope.
|
|
86
101
|
CONFLICT_MARKER_FILES=""
|
|
102
|
+
if [ "${GATE_12_DOCS_ONLY:-false}" = true ]; then
|
|
103
|
+
if git diff --cached --no-color --diff-filter=d 2>/dev/null \
|
|
104
|
+
| grep -E '^\+' | grep -v '^+++' | grep -qE '^\+(<<<<<<<|=======|>>>>>>>)'; then
|
|
105
|
+
CONFLICT_MARKER_FILES="$STAGED_FILES"
|
|
106
|
+
fi
|
|
107
|
+
else
|
|
87
108
|
while IFS= read -r file; do
|
|
88
109
|
[ -z "$file" ] && continue
|
|
89
110
|
# Skip binary files
|
|
@@ -97,6 +118,7 @@ while IFS= read -r file; do
|
|
|
97
118
|
done <<EOF
|
|
98
119
|
$STAGED_FILES
|
|
99
120
|
EOF
|
|
121
|
+
fi
|
|
100
122
|
|
|
101
123
|
if [ -n "$CONFLICT_MARKER_FILES" ]; then
|
|
102
124
|
echo " ❌ Merge conflict markers detected in:"
|
|
@@ -109,6 +131,7 @@ fi
|
|
|
109
131
|
|
|
110
132
|
# --- Check 4: Oversized files ---
|
|
111
133
|
OVERSIZED_FILES=""
|
|
134
|
+
if [ "${GATE_12_DOCS_ONLY:-false}" != true ]; then
|
|
112
135
|
while IFS= read -r file; do
|
|
113
136
|
[ -z "$file" ] && continue
|
|
114
137
|
[ ! -f "$file" ] && continue
|
|
@@ -120,6 +143,7 @@ while IFS= read -r file; do
|
|
|
120
143
|
done <<EOF
|
|
121
144
|
$STAGED_FILES
|
|
122
145
|
EOF
|
|
146
|
+
fi
|
|
123
147
|
|
|
124
148
|
if [ -n "$OVERSIZED_FILES" ]; then
|
|
125
149
|
echo " ⚠️ Oversized files (>${MAX_FILE_SIZE_BYTES} bytes):"
|
|
@@ -150,6 +174,10 @@ elif command -v jq >/dev/null 2>&1; then
|
|
|
150
174
|
JSON_CMD="jq"
|
|
151
175
|
fi
|
|
152
176
|
|
|
177
|
+
# Check which staged YAML/JSON files actually changed (REQ-F3: a repo with 40
|
|
178
|
+
# YAML configs but one edited file validates ONE blob, not forty).
|
|
179
|
+
CHANGED_YAML_JSON=$(git diff --cached --name-only --diff-filter=d -- '*.yml' '*.yaml' '*.json' 2>/dev/null)
|
|
180
|
+
|
|
153
181
|
while IFS= read -r file; do
|
|
154
182
|
[ -z "$file" ] && continue
|
|
155
183
|
[ ! -f "$file" ] && continue
|
|
@@ -192,7 +220,7 @@ let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{JSON.p
|
|
|
192
220
|
;;
|
|
193
221
|
esac
|
|
194
222
|
done <<EOF
|
|
195
|
-
$
|
|
223
|
+
$CHANGED_YAML_JSON
|
|
196
224
|
EOF
|
|
197
225
|
|
|
198
226
|
if [ -n "$YAML_JSON_ERRORS" ]; then
|
|
@@ -207,6 +235,12 @@ elif [ "$YAML_JSON_CHECK_COUNT" -gt 0 ]; then
|
|
|
207
235
|
fi
|
|
208
236
|
|
|
209
237
|
# --- Result ---
|
|
238
|
+
if [ "${GATE_12_DOCS_ONLY:-false}" = true ] && [ "$HYGIENE_ISSUES" -eq 0 ]; then
|
|
239
|
+
echo " ✅ PASSED - File Hygiene (docs-only commit: diff-scoped conflict-marker + YAML/JSON checks clean)."
|
|
240
|
+
GATE_12_STATUS="PASS"
|
|
241
|
+
record_gate_audit "gate-12" "file-hygiene" "$GATE_12_STATUS" "0" "$GATE_12_START" "" "docs_only_change"
|
|
242
|
+
return 0 2>/dev/null || true
|
|
243
|
+
fi
|
|
210
244
|
if [ "$HYGIENE_ISSUES" -eq 0 ]; then
|
|
211
245
|
echo " ✅ PASSED - No file hygiene issues detected."
|
|
212
246
|
GATE_12_STATUS="PASS"
|
package/gate-3.sh
CHANGED
|
@@ -92,7 +92,76 @@ else
|
|
|
92
92
|
[ -n "$_cc_file" ] && CC_FILES_ARGS+=("$_cc_file")
|
|
93
93
|
done <<< "$CC_FILES"
|
|
94
94
|
CC_OUTPUT=$("$LIZARD_PATH" -C "$CCN_THRESHOLD" ${CC_FILES_ARGS[@]+"${CC_FILES_ARGS[@]}"} 2>&1 || true)
|
|
95
|
-
|
|
95
|
+
|
|
96
|
+
# REQ-D (sprint-20261018-quality): baseline ratchet. When
|
|
97
|
+
# .ccn-baseline.json exists it replaces the flat threshold verdict:
|
|
98
|
+
# recorded functions may not get WORSE (strictly greater blocks), new
|
|
99
|
+
# functions face the live thresholds, improvements only hint. The
|
|
100
|
+
# legacy path below stays byte-for-byte for repos without a baseline.
|
|
101
|
+
if [ -f ".ccn-baseline.json" ]; then
|
|
102
|
+
# #544: lizard's raw CCN is not what the baseline was recorded with.
|
|
103
|
+
# The baseline comes from scripts/ccn-report.cjs, which re-derives every
|
|
104
|
+
# above-threshold CCN from brace-balanced source (lizard's TS parser
|
|
105
|
+
# loses braces on generic-arrow fields and charges neighbours). Feeding
|
|
106
|
+
# the ratchet raw numbers compared two different engines — a function
|
|
107
|
+
# at its recorded ceiling read as "worse" purely from span inflation.
|
|
108
|
+
# Route the CSV through the same verifier; on rows it cannot verify the
|
|
109
|
+
# helper exits non-zero, and this gate fails closed instead of passing
|
|
110
|
+
# an unvalidated number.
|
|
111
|
+
CC_CSV=$("$LIZARD_PATH" --csv ${CC_FILES_ARGS[@]+"${CC_FILES_ARGS[@]}"} 2>/dev/null || true)
|
|
112
|
+
CC_REPORT="$(git rev-parse --show-toplevel 2>/dev/null)/scripts/ccn-report.cjs"
|
|
113
|
+
# Fail closed when the verifier is unavailable. Previously this fell
|
|
114
|
+
# through and graded the RAW lizard CSV, while CI hard-fails on the same
|
|
115
|
+
# condition — so the hook and CI could reach opposite verdicts on
|
|
116
|
+
# identical code, and the hook silently compared baseline numbers
|
|
117
|
+
# (recorded by ccn-report's brace-verified engine) against an engine it
|
|
118
|
+
# no longer had. Agreeing with CI is the point of having both.
|
|
119
|
+
if [ ! -f "$CC_REPORT" ] || ! command -v node >/dev/null 2>&1; then
|
|
120
|
+
echo "❌ BLOCKED - complexity verifier unavailable (scripts/ccn-report.cjs needs node)."
|
|
121
|
+
echo "CI fails on this same condition; refusing to grade unverified CCN locally."
|
|
122
|
+
GATE_3_DETAIL="mode=ratchet verifier=MISSING"
|
|
123
|
+
exit 1
|
|
124
|
+
fi
|
|
125
|
+
if [ -f "$CC_REPORT" ] && command -v node >/dev/null 2>&1; then
|
|
126
|
+
_cc_verify_failed=0
|
|
127
|
+
CC_CSV_VERIFIED=$(printf '%s\n' "$CC_CSV" | node "$CC_REPORT" --csv-verified batch --root "$(git rev-parse --show-toplevel 2>/dev/null)") || _cc_verify_failed=1
|
|
128
|
+
if [ "$_cc_verify_failed" -ne 0 ]; then
|
|
129
|
+
echo "❌ BLOCKED - complexity could not be span-verified; refusing to grade unverified CCN."
|
|
130
|
+
GATE_3_DETAIL="mode=ratchet verify=FAILED"
|
|
131
|
+
exit 1
|
|
132
|
+
fi
|
|
133
|
+
CC_CSV="$CC_CSV_VERIFIED"
|
|
134
|
+
fi
|
|
135
|
+
RATCHET_LIB="$GATE_DIR/lib/ccn-ratchet.sh"
|
|
136
|
+
[ -f "$RATCHET_LIB" ] || RATCHET_LIB="$(git rev-parse --show-toplevel 2>/dev/null)/githooks/lib/ccn-ratchet.sh"
|
|
137
|
+
# shellcheck source=/dev/null
|
|
138
|
+
. "$RATCHET_LIB"
|
|
139
|
+
# NOTE: the thresholds must be plain assignments BEFORE the pipe. As a
|
|
140
|
+
# prefix to a variable assignment (`T=1 V=$(... | fn)`) they land in the
|
|
141
|
+
# assignment's subshell, so the function sees no thresholds AND reads an
|
|
142
|
+
# empty stdin — the verdict comes back empty and Gate 3 passes on zero
|
|
143
|
+
# rows (measured while wiring #544: same input passed or blocked
|
|
144
|
+
# depending only on where the prefix sat).
|
|
145
|
+
CCN_WARN_THRESHOLD="$CCN_THRESHOLD"
|
|
146
|
+
CCN_BLOCK_THRESHOLD=$((CCN_THRESHOLD * 2))
|
|
147
|
+
RATCHET_VERDICT=$(printf '%s\n' "$CC_CSV" | evaluate_ccn_ratchet ".ccn-baseline.json")
|
|
148
|
+
CC_VIOLATIONS=$(printf '%s\n' "$RATCHET_VERDICT" | grep -c '^BLOCK ' || true)
|
|
149
|
+
CC_LOWER=$(printf '%s\n' "$RATCHET_VERDICT" | grep -c '^LOWER ' || true)
|
|
150
|
+
printf '%s\n' "$RATCHET_VERDICT" | grep -v '^OK ' || true
|
|
151
|
+
if [ "${CC_LOWER:-0}" -gt 0 ]; then
|
|
152
|
+
echo "ℹ️ $CC_LOWER function(s) improved below baseline — refresh with: node scripts/ccn-report.cjs --write-baseline"
|
|
153
|
+
fi
|
|
154
|
+
GATE_3_DETAIL="mode=ratchet"
|
|
155
|
+
if [ "${CC_VIOLATIONS:-0}" -gt 0 ]; then
|
|
156
|
+
echo ""
|
|
157
|
+
echo "❌ BLOCKED - $CC_VIOLATIONS function(s) violate the complexity ratchet (.ccn-baseline.json)."
|
|
158
|
+
echo "Revert the increase, or refactor; refresh the baseline only via ccn-baseline-refresh commits."
|
|
159
|
+
exit 1
|
|
160
|
+
else
|
|
161
|
+
echo "✅ PASSED - Complexity within ratchet baseline."
|
|
162
|
+
GATE_3_STATUS="PASS"
|
|
163
|
+
fi
|
|
164
|
+
else
|
|
96
165
|
# Parse warning count from the summary table: "Warning cnt 8"
|
|
97
166
|
# Use anchored grep to avoid matching lizard table headers (e.g. "Rt" column)
|
|
98
167
|
CC_WARNINGS=$(echo "$CC_OUTPUT" | grep "^Warning cnt" | awk '{print $NF}' | tr -d '[:space:]' | sed 's/[^0-9]//g' || true)
|
|
@@ -108,6 +177,7 @@ else
|
|
|
108
177
|
echo "✅ PASSED - All functions within complexity threshold ($CCN_THRESHOLD)."
|
|
109
178
|
GATE_3_STATUS="PASS"
|
|
110
179
|
fi
|
|
180
|
+
fi
|
|
111
181
|
else
|
|
112
182
|
echo "⏭️ SKIPPED - Complexity (no source files to check)."
|
|
113
183
|
GATE_3_STATUS="SKIP"
|
|
@@ -121,4 +191,10 @@ else
|
|
|
121
191
|
GATE_3_REASON="tool_not_installed"
|
|
122
192
|
fi
|
|
123
193
|
fi
|
|
124
|
-
|
|
194
|
+
# The audit's issues_found comes from whichever mode actually ran. In ratchet
|
|
195
|
+
# mode the count lives in CC_VIOLATIONS (functions worse than their recorded
|
|
196
|
+
# baseline); the flat-threshold mode parses CC_WARNINGS from lizard's own table.
|
|
197
|
+
# Reading only CC_WARNINGS meant every ratchet block recorded issues_found=0 —
|
|
198
|
+
# the gate failed while the journal claimed nothing was found.
|
|
199
|
+
CC_ISSUES="${CC_VIOLATIONS:-${CC_WARNINGS:-0}}"
|
|
200
|
+
record_gate_audit "gate-3" "complexity" "$GATE_3_STATUS" "$CC_ISSUES" "$GATE_3_START" "ccn_threshold=${CCN_THRESHOLD:-5}${GATE_3_DETAIL:+ $GATE_3_DETAIL}"
|
package/gate-4.sh
CHANGED
|
File without changes
|
package/gate-7.sh
CHANGED
|
File without changes
|
package/gate-8.sh
CHANGED
|
File without changes
|
package/gate-9.sh
CHANGED
|
@@ -57,8 +57,19 @@ else
|
|
|
57
57
|
# CR), so one line is exactly one path. Asserted for spaces and a leading '-'
|
|
58
58
|
# by AC-490-04.
|
|
59
59
|
SEMGREP_ARGS=()
|
|
60
|
+
SEMGREP_FILE_COUNT=0
|
|
61
|
+
SEMGREP_DIRS_SEEN=$'\n'
|
|
60
62
|
while IFS= read -r _semgrep_file; do
|
|
61
|
-
[ -n "$_semgrep_file" ]
|
|
63
|
+
[ -n "$_semgrep_file" ] || continue
|
|
64
|
+
SEMGREP_FILE_COUNT=$((SEMGREP_FILE_COUNT + 1))
|
|
65
|
+
SEMGREP_ARGS+=("--include=${_semgrep_file##*/}")
|
|
66
|
+
_semgrep_dir="./${_semgrep_file%/*}"
|
|
67
|
+
# A repo-root file has no directory component ("./x.ts" strips to "./").
|
|
68
|
+
[ "$_semgrep_dir" = "./" ] && _semgrep_dir="."
|
|
69
|
+
case "$SEMGREP_DIRS_SEEN" in
|
|
70
|
+
*"$_semgrep_dir"*) : ;;
|
|
71
|
+
*) SEMGREP_ARGS+=("$_semgrep_dir"); SEMGREP_DIRS_SEEN="$SEMGREP_DIRS_SEEN$_semgrep_dir"$'\n' ;;
|
|
72
|
+
esac
|
|
62
73
|
done <<EOF
|
|
63
74
|
$SEMGREP_FILES
|
|
64
75
|
EOF
|
|
@@ -173,7 +184,7 @@ console.log("");});
|
|
|
173
184
|
exit 1
|
|
174
185
|
else
|
|
175
186
|
echo ""
|
|
176
|
-
echo " ✅ PASSED - No blocking vulnerabilities (files scanned: $
|
|
187
|
+
echo " ✅ PASSED - No blocking vulnerabilities (files scanned: $SEMGREP_FILE_COUNT)"
|
|
177
188
|
if [ "$V_WARNINGS" -gt 0 ]; then
|
|
178
189
|
echo " ⚠️ ${V_WARNINGS} non-blocking findings (warnings only)"
|
|
179
190
|
echo "$FINDING_DETAILS"
|
package/hooks/adapter-common.sh
CHANGED
|
File without changes
|
package/hooks/gate-10.sh
CHANGED
|
@@ -53,12 +53,31 @@ else
|
|
|
53
53
|
# lines to `read`; bash never rescans the result for separators. Its correct
|
|
54
54
|
# reading rests on one property of the producer above, not of the loop:
|
|
55
55
|
# `git diff --cached --name-only` terminates paths with LF and quotes control
|
|
56
|
-
# characters (a CR in a name arrives as the four bytes \215, never as a bare
|
|
57
|
-
# CR), so one line is exactly one path. Asserted for spaces and a leading '-'
|
|
58
56
|
# by AC-490-04.
|
|
57
|
+
#
|
|
58
|
+
# #543: semgrep 1.171 ignores .semgrepignore ENTIRELY when files are passed
|
|
59
|
+
# as explicit targets (measured: file target -> an ignored finding survives
|
|
60
|
+
# every suppression form — ignore patterns, --exclude, --exclude-rule,
|
|
61
|
+
# --project-root; directory target + --include -> the ignore file is honored,
|
|
62
|
+
# proven fail-closed by removing it and watching the findings return). The
|
|
63
|
+
# gate therefore passes each staged file's DIRECTORY as a target plus one
|
|
64
|
+
# --include per staged basename. Coverage stays exact for the staged set
|
|
65
|
+
# (verified: scanned == staged); the only widening is same-basename siblings
|
|
66
|
+
# outside the staged set, which scan too — scanning MORE is the safe side.
|
|
59
67
|
SEMGREP_ARGS=()
|
|
68
|
+
SEMGREP_FILE_COUNT=0
|
|
69
|
+
SEMGREP_DIRS_SEEN=$'\n'
|
|
60
70
|
while IFS= read -r _semgrep_file; do
|
|
61
|
-
[ -n "$_semgrep_file" ]
|
|
71
|
+
[ -n "$_semgrep_file" ] || continue
|
|
72
|
+
SEMGREP_FILE_COUNT=$((SEMGREP_FILE_COUNT + 1))
|
|
73
|
+
SEMGREP_ARGS+=("--include=${_semgrep_file##*/}")
|
|
74
|
+
_semgrep_dir="./${_semgrep_file%/*}"
|
|
75
|
+
# A repo-root file has no directory component ("./x.ts" strips to "./").
|
|
76
|
+
[ "$_semgrep_dir" = "./" ] && _semgrep_dir="."
|
|
77
|
+
case "$SEMGREP_DIRS_SEEN" in
|
|
78
|
+
*"$_semgrep_dir"*) : ;;
|
|
79
|
+
*) SEMGREP_ARGS+=("$_semgrep_dir"); SEMGREP_DIRS_SEEN="$SEMGREP_DIRS_SEEN$_semgrep_dir"$'\n' ;;
|
|
80
|
+
esac
|
|
62
81
|
done <<EOF
|
|
63
82
|
$SEMGREP_FILES
|
|
64
83
|
EOF
|
|
@@ -173,12 +192,12 @@ console.log("");});
|
|
|
173
192
|
echo "$FINDING_DETAILS"
|
|
174
193
|
echo " Run 'semgrep scan --config=p/security-audit' to review all findings."
|
|
175
194
|
GATE_10_STATUS="FAIL"
|
|
176
|
-
GATE_10_SCANNED_FILES=$
|
|
195
|
+
GATE_10_SCANNED_FILES=$SEMGREP_FILE_COUNT
|
|
177
196
|
exit 1
|
|
178
197
|
else
|
|
179
|
-
GATE_10_SCANNED_FILES=$
|
|
198
|
+
GATE_10_SCANNED_FILES=$SEMGREP_FILE_COUNT
|
|
180
199
|
echo ""
|
|
181
|
-
echo " ✅ PASSED - No blocking vulnerabilities (files scanned: $
|
|
200
|
+
echo " ✅ PASSED - No blocking vulnerabilities (files scanned: $SEMGREP_FILE_COUNT)"
|
|
182
201
|
if [ "$V_WARNINGS" -gt 0 ]; then
|
|
183
202
|
echo " ⚠️ ${V_WARNINGS} non-blocking findings (warnings only)"
|
|
184
203
|
echo "$FINDING_DETAILS"
|
|
@@ -25,11 +25,20 @@ if [ -z "$STAGED_FILES" ]; then
|
|
|
25
25
|
return 0 2>/dev/null || true
|
|
26
26
|
fi
|
|
27
27
|
|
|
28
|
+
# REQ-F2: a docs-only commit cannot introduce code regressions; conflict
|
|
29
|
+
# markers and YAML/JSON syntax are still checked (diff-scoped below).
|
|
30
|
+
if [ "${DOCS_ONLY_COMMIT:-false}" = true ]; then
|
|
31
|
+
echo " ⏭️ SKIPPED - File hygiene heavy checks (documentation-only commit; diff-scoped checks still run)."
|
|
32
|
+
GATE_12_STATUS="SKIP"
|
|
33
|
+
GATE_12_DOCS_ONLY=true
|
|
34
|
+
fi
|
|
35
|
+
|
|
28
36
|
HYGIENE_ISSUES=0
|
|
29
37
|
HYGIENE_FILES=""
|
|
30
38
|
|
|
31
39
|
# --- Check 1: Trailing whitespace ---
|
|
32
40
|
TRAILING_WS_FILES=""
|
|
41
|
+
if [ "${GATE_12_DOCS_ONLY:-false}" != true ]; then
|
|
33
42
|
while IFS= read -r file; do
|
|
34
43
|
[ -z "$file" ] && continue
|
|
35
44
|
# Skip binary files by checking if file contains null bytes
|
|
@@ -43,6 +52,7 @@ while IFS= read -r file; do
|
|
|
43
52
|
done <<EOF
|
|
44
53
|
$STAGED_FILES
|
|
45
54
|
EOF
|
|
55
|
+
fi
|
|
46
56
|
|
|
47
57
|
if [ -n "$TRAILING_WS_FILES" ]; then
|
|
48
58
|
echo " ⚠️ Trailing whitespace detected in:"
|
|
@@ -55,6 +65,7 @@ fi
|
|
|
55
65
|
|
|
56
66
|
# --- Check 2: Missing EOF newline ---
|
|
57
67
|
NO_EOF_NEWLINE_FILES=""
|
|
68
|
+
if [ "${GATE_12_DOCS_ONLY:-false}" != true ]; then
|
|
58
69
|
while IFS= read -r file; do
|
|
59
70
|
[ -z "$file" ] && continue
|
|
60
71
|
# Skip binary files
|
|
@@ -72,6 +83,7 @@ while IFS= read -r file; do
|
|
|
72
83
|
done <<EOF
|
|
73
84
|
$STAGED_FILES
|
|
74
85
|
EOF
|
|
86
|
+
fi
|
|
75
87
|
|
|
76
88
|
if [ -n "$NO_EOF_NEWLINE_FILES" ]; then
|
|
77
89
|
echo " ⚠️ Missing EOF newline in:"
|
|
@@ -83,7 +95,16 @@ if [ -n "$NO_EOF_NEWLINE_FILES" ]; then
|
|
|
83
95
|
fi
|
|
84
96
|
|
|
85
97
|
# --- Check 3: Merge conflict markers ---
|
|
98
|
+
# Runs in BOTH modes. Docs-only commits read the staged DIFF (hunk cost)
|
|
99
|
+
# instead of every full blob — a marker line must be ADDED to matter, so
|
|
100
|
+
# '^+' minus '+++' headers is the precise scope.
|
|
86
101
|
CONFLICT_MARKER_FILES=""
|
|
102
|
+
if [ "${GATE_12_DOCS_ONLY:-false}" = true ]; then
|
|
103
|
+
if git diff --cached --no-color --diff-filter=d 2>/dev/null \
|
|
104
|
+
| grep -E '^\+' | grep -v '^+++' | grep -qE '^\+(<<<<<<<|=======|>>>>>>>)'; then
|
|
105
|
+
CONFLICT_MARKER_FILES="$STAGED_FILES"
|
|
106
|
+
fi
|
|
107
|
+
else
|
|
87
108
|
while IFS= read -r file; do
|
|
88
109
|
[ -z "$file" ] && continue
|
|
89
110
|
# Skip binary files
|
|
@@ -97,6 +118,7 @@ while IFS= read -r file; do
|
|
|
97
118
|
done <<EOF
|
|
98
119
|
$STAGED_FILES
|
|
99
120
|
EOF
|
|
121
|
+
fi
|
|
100
122
|
|
|
101
123
|
if [ -n "$CONFLICT_MARKER_FILES" ]; then
|
|
102
124
|
echo " ❌ Merge conflict markers detected in:"
|
|
@@ -109,6 +131,7 @@ fi
|
|
|
109
131
|
|
|
110
132
|
# --- Check 4: Oversized files ---
|
|
111
133
|
OVERSIZED_FILES=""
|
|
134
|
+
if [ "${GATE_12_DOCS_ONLY:-false}" != true ]; then
|
|
112
135
|
while IFS= read -r file; do
|
|
113
136
|
[ -z "$file" ] && continue
|
|
114
137
|
[ ! -f "$file" ] && continue
|
|
@@ -120,6 +143,7 @@ while IFS= read -r file; do
|
|
|
120
143
|
done <<EOF
|
|
121
144
|
$STAGED_FILES
|
|
122
145
|
EOF
|
|
146
|
+
fi
|
|
123
147
|
|
|
124
148
|
if [ -n "$OVERSIZED_FILES" ]; then
|
|
125
149
|
echo " ⚠️ Oversized files (>${MAX_FILE_SIZE_BYTES} bytes):"
|
|
@@ -150,6 +174,10 @@ elif command -v jq >/dev/null 2>&1; then
|
|
|
150
174
|
JSON_CMD="jq"
|
|
151
175
|
fi
|
|
152
176
|
|
|
177
|
+
# Check which staged YAML/JSON files actually changed (REQ-F3: a repo with 40
|
|
178
|
+
# YAML configs but one edited file validates ONE blob, not forty).
|
|
179
|
+
CHANGED_YAML_JSON=$(git diff --cached --name-only --diff-filter=d -- '*.yml' '*.yaml' '*.json' 2>/dev/null)
|
|
180
|
+
|
|
153
181
|
while IFS= read -r file; do
|
|
154
182
|
[ -z "$file" ] && continue
|
|
155
183
|
[ ! -f "$file" ] && continue
|
|
@@ -192,7 +220,7 @@ let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{JSON.p
|
|
|
192
220
|
;;
|
|
193
221
|
esac
|
|
194
222
|
done <<EOF
|
|
195
|
-
$
|
|
223
|
+
$CHANGED_YAML_JSON
|
|
196
224
|
EOF
|
|
197
225
|
|
|
198
226
|
if [ -n "$YAML_JSON_ERRORS" ]; then
|
|
@@ -207,6 +235,12 @@ elif [ "$YAML_JSON_CHECK_COUNT" -gt 0 ]; then
|
|
|
207
235
|
fi
|
|
208
236
|
|
|
209
237
|
# --- Result ---
|
|
238
|
+
if [ "${GATE_12_DOCS_ONLY:-false}" = true ] && [ "$HYGIENE_ISSUES" -eq 0 ]; then
|
|
239
|
+
echo " ✅ PASSED - File Hygiene (docs-only commit: diff-scoped conflict-marker + YAML/JSON checks clean)."
|
|
240
|
+
GATE_12_STATUS="PASS"
|
|
241
|
+
record_gate_audit "gate-12" "file-hygiene" "$GATE_12_STATUS" "0" "$GATE_12_START" "" "docs_only_change"
|
|
242
|
+
return 0 2>/dev/null || true
|
|
243
|
+
fi
|
|
210
244
|
if [ "$HYGIENE_ISSUES" -eq 0 ]; then
|
|
211
245
|
echo " ✅ PASSED - No file hygiene issues detected."
|
|
212
246
|
GATE_12_STATUS="PASS"
|
package/hooks/gate-3.sh
CHANGED
|
@@ -92,7 +92,76 @@ else
|
|
|
92
92
|
[ -n "$_cc_file" ] && CC_FILES_ARGS+=("$_cc_file")
|
|
93
93
|
done <<< "$CC_FILES"
|
|
94
94
|
CC_OUTPUT=$("$LIZARD_PATH" -C "$CCN_THRESHOLD" ${CC_FILES_ARGS[@]+"${CC_FILES_ARGS[@]}"} 2>&1 || true)
|
|
95
|
-
|
|
95
|
+
|
|
96
|
+
# REQ-D (sprint-20261018-quality): baseline ratchet. When
|
|
97
|
+
# .ccn-baseline.json exists it replaces the flat threshold verdict:
|
|
98
|
+
# recorded functions may not get WORSE (strictly greater blocks), new
|
|
99
|
+
# functions face the live thresholds, improvements only hint. The
|
|
100
|
+
# legacy path below stays byte-for-byte for repos without a baseline.
|
|
101
|
+
if [ -f ".ccn-baseline.json" ]; then
|
|
102
|
+
# #544: lizard's raw CCN is not what the baseline was recorded with.
|
|
103
|
+
# The baseline comes from scripts/ccn-report.cjs, which re-derives every
|
|
104
|
+
# above-threshold CCN from brace-balanced source (lizard's TS parser
|
|
105
|
+
# loses braces on generic-arrow fields and charges neighbours). Feeding
|
|
106
|
+
# the ratchet raw numbers compared two different engines — a function
|
|
107
|
+
# at its recorded ceiling read as "worse" purely from span inflation.
|
|
108
|
+
# Route the CSV through the same verifier; on rows it cannot verify the
|
|
109
|
+
# helper exits non-zero, and this gate fails closed instead of passing
|
|
110
|
+
# an unvalidated number.
|
|
111
|
+
CC_CSV=$("$LIZARD_PATH" --csv ${CC_FILES_ARGS[@]+"${CC_FILES_ARGS[@]}"} 2>/dev/null || true)
|
|
112
|
+
CC_REPORT="$(git rev-parse --show-toplevel 2>/dev/null)/scripts/ccn-report.cjs"
|
|
113
|
+
# Fail closed when the verifier is unavailable. Previously this fell
|
|
114
|
+
# through and graded the RAW lizard CSV, while CI hard-fails on the same
|
|
115
|
+
# condition — so the hook and CI could reach opposite verdicts on
|
|
116
|
+
# identical code, and the hook silently compared baseline numbers
|
|
117
|
+
# (recorded by ccn-report's brace-verified engine) against an engine it
|
|
118
|
+
# no longer had. Agreeing with CI is the point of having both.
|
|
119
|
+
if [ ! -f "$CC_REPORT" ] || ! command -v node >/dev/null 2>&1; then
|
|
120
|
+
echo "❌ BLOCKED - complexity verifier unavailable (scripts/ccn-report.cjs needs node)."
|
|
121
|
+
echo "CI fails on this same condition; refusing to grade unverified CCN locally."
|
|
122
|
+
GATE_3_DETAIL="mode=ratchet verifier=MISSING"
|
|
123
|
+
exit 1
|
|
124
|
+
fi
|
|
125
|
+
if [ -f "$CC_REPORT" ] && command -v node >/dev/null 2>&1; then
|
|
126
|
+
_cc_verify_failed=0
|
|
127
|
+
CC_CSV_VERIFIED=$(printf '%s\n' "$CC_CSV" | node "$CC_REPORT" --csv-verified batch --root "$(git rev-parse --show-toplevel 2>/dev/null)") || _cc_verify_failed=1
|
|
128
|
+
if [ "$_cc_verify_failed" -ne 0 ]; then
|
|
129
|
+
echo "❌ BLOCKED - complexity could not be span-verified; refusing to grade unverified CCN."
|
|
130
|
+
GATE_3_DETAIL="mode=ratchet verify=FAILED"
|
|
131
|
+
exit 1
|
|
132
|
+
fi
|
|
133
|
+
CC_CSV="$CC_CSV_VERIFIED"
|
|
134
|
+
fi
|
|
135
|
+
RATCHET_LIB="$GATE_DIR/lib/ccn-ratchet.sh"
|
|
136
|
+
[ -f "$RATCHET_LIB" ] || RATCHET_LIB="$(git rev-parse --show-toplevel 2>/dev/null)/githooks/lib/ccn-ratchet.sh"
|
|
137
|
+
# shellcheck source=/dev/null
|
|
138
|
+
. "$RATCHET_LIB"
|
|
139
|
+
# NOTE: the thresholds must be plain assignments BEFORE the pipe. As a
|
|
140
|
+
# prefix to a variable assignment (`T=1 V=$(... | fn)`) they land in the
|
|
141
|
+
# assignment's subshell, so the function sees no thresholds AND reads an
|
|
142
|
+
# empty stdin — the verdict comes back empty and Gate 3 passes on zero
|
|
143
|
+
# rows (measured while wiring #544: same input passed or blocked
|
|
144
|
+
# depending only on where the prefix sat).
|
|
145
|
+
CCN_WARN_THRESHOLD="$CCN_THRESHOLD"
|
|
146
|
+
CCN_BLOCK_THRESHOLD=$((CCN_THRESHOLD * 2))
|
|
147
|
+
RATCHET_VERDICT=$(printf '%s\n' "$CC_CSV" | evaluate_ccn_ratchet ".ccn-baseline.json")
|
|
148
|
+
CC_VIOLATIONS=$(printf '%s\n' "$RATCHET_VERDICT" | grep -c '^BLOCK ' || true)
|
|
149
|
+
CC_LOWER=$(printf '%s\n' "$RATCHET_VERDICT" | grep -c '^LOWER ' || true)
|
|
150
|
+
printf '%s\n' "$RATCHET_VERDICT" | grep -v '^OK ' || true
|
|
151
|
+
if [ "${CC_LOWER:-0}" -gt 0 ]; then
|
|
152
|
+
echo "ℹ️ $CC_LOWER function(s) improved below baseline — refresh with: node scripts/ccn-report.cjs --write-baseline"
|
|
153
|
+
fi
|
|
154
|
+
GATE_3_DETAIL="mode=ratchet"
|
|
155
|
+
if [ "${CC_VIOLATIONS:-0}" -gt 0 ]; then
|
|
156
|
+
echo ""
|
|
157
|
+
echo "❌ BLOCKED - $CC_VIOLATIONS function(s) violate the complexity ratchet (.ccn-baseline.json)."
|
|
158
|
+
echo "Revert the increase, or refactor; refresh the baseline only via ccn-baseline-refresh commits."
|
|
159
|
+
exit 1
|
|
160
|
+
else
|
|
161
|
+
echo "✅ PASSED - Complexity within ratchet baseline."
|
|
162
|
+
GATE_3_STATUS="PASS"
|
|
163
|
+
fi
|
|
164
|
+
else
|
|
96
165
|
# Parse warning count from the summary table: "Warning cnt 8"
|
|
97
166
|
# Use anchored grep to avoid matching lizard table headers (e.g. "Rt" column)
|
|
98
167
|
CC_WARNINGS=$(echo "$CC_OUTPUT" | grep "^Warning cnt" | awk '{print $NF}' | tr -d '[:space:]' | sed 's/[^0-9]//g' || true)
|
|
@@ -108,6 +177,7 @@ else
|
|
|
108
177
|
echo "✅ PASSED - All functions within complexity threshold ($CCN_THRESHOLD)."
|
|
109
178
|
GATE_3_STATUS="PASS"
|
|
110
179
|
fi
|
|
180
|
+
fi
|
|
111
181
|
else
|
|
112
182
|
echo "⏭️ SKIPPED - Complexity (no source files to check)."
|
|
113
183
|
GATE_3_STATUS="SKIP"
|
|
@@ -121,4 +191,10 @@ else
|
|
|
121
191
|
GATE_3_REASON="tool_not_installed"
|
|
122
192
|
fi
|
|
123
193
|
fi
|
|
124
|
-
|
|
194
|
+
# The audit's issues_found comes from whichever mode actually ran. In ratchet
|
|
195
|
+
# mode the count lives in CC_VIOLATIONS (functions worse than their recorded
|
|
196
|
+
# baseline); the flat-threshold mode parses CC_WARNINGS from lizard's own table.
|
|
197
|
+
# Reading only CC_WARNINGS meant every ratchet block recorded issues_found=0 —
|
|
198
|
+
# the gate failed while the journal claimed nothing was found.
|
|
199
|
+
CC_ISSUES="${CC_VIOLATIONS:-${CC_WARNINGS:-0}}"
|
|
200
|
+
record_gate_audit "gate-3" "complexity" "$GATE_3_STATUS" "$CC_ISSUES" "$GATE_3_START" "ccn_threshold=${CCN_THRESHOLD:-5}${GATE_3_DETAIL:+ $GATE_3_DETAIL}"
|
package/hooks/gate-4.sh
CHANGED
|
File without changes
|
package/hooks/gate-7.sh
CHANGED
|
File without changes
|
package/hooks/gate-8.sh
CHANGED
|
File without changes
|
package/hooks/gate-9.sh
CHANGED
|
@@ -57,8 +57,19 @@ else
|
|
|
57
57
|
# CR), so one line is exactly one path. Asserted for spaces and a leading '-'
|
|
58
58
|
# by AC-490-04.
|
|
59
59
|
SEMGREP_ARGS=()
|
|
60
|
+
SEMGREP_FILE_COUNT=0
|
|
61
|
+
SEMGREP_DIRS_SEEN=$'\n'
|
|
60
62
|
while IFS= read -r _semgrep_file; do
|
|
61
|
-
[ -n "$_semgrep_file" ]
|
|
63
|
+
[ -n "$_semgrep_file" ] || continue
|
|
64
|
+
SEMGREP_FILE_COUNT=$((SEMGREP_FILE_COUNT + 1))
|
|
65
|
+
SEMGREP_ARGS+=("--include=${_semgrep_file##*/}")
|
|
66
|
+
_semgrep_dir="./${_semgrep_file%/*}"
|
|
67
|
+
# A repo-root file has no directory component ("./x.ts" strips to "./").
|
|
68
|
+
[ "$_semgrep_dir" = "./" ] && _semgrep_dir="."
|
|
69
|
+
case "$SEMGREP_DIRS_SEEN" in
|
|
70
|
+
*"$_semgrep_dir"*) : ;;
|
|
71
|
+
*) SEMGREP_ARGS+=("$_semgrep_dir"); SEMGREP_DIRS_SEEN="$SEMGREP_DIRS_SEEN$_semgrep_dir"$'\n' ;;
|
|
72
|
+
esac
|
|
62
73
|
done <<EOF
|
|
63
74
|
$SEMGREP_FILES
|
|
64
75
|
EOF
|
|
@@ -173,7 +184,7 @@ console.log("");});
|
|
|
173
184
|
exit 1
|
|
174
185
|
else
|
|
175
186
|
echo ""
|
|
176
|
-
echo " ✅ PASSED - No blocking vulnerabilities (files scanned: $
|
|
187
|
+
echo " ✅ PASSED - No blocking vulnerabilities (files scanned: $SEMGREP_FILE_COUNT)"
|
|
177
188
|
if [ "$V_WARNINGS" -gt 0 ]; then
|
|
178
189
|
echo " ⚠️ ${V_WARNINGS} non-blocking findings (warnings only)"
|
|
179
190
|
echo "$FINDING_DETAILS"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# audit-sanitize.sh — honest gate-duration bookkeeping (REQ-B, #536 family)
|
|
4
|
+
#
|
|
5
|
+
# .xp-gate/audit.jsonl carried 389 records whose duration_ms was an epoch-
|
|
6
|
+
# sized integer: a poisoned start_ms (empty -> arithmetic 0, or a SECONDS-unit
|
|
7
|
+
# value from a pre-#370 code path) turned `end - start` into "now". The old
|
|
8
|
+
# handler clamped negatives to 0 and flagged >2h runs with duration_anomaly,
|
|
9
|
+
# but still wrote the astronomical number itself. Fail closed on the input
|
|
10
|
+
# instead: when start_ms cannot be a millisecond epoch, record null + reason
|
|
11
|
+
# so every consumer can exclude the sample instead of averaging a lie.
|
|
12
|
+
#
|
|
13
|
+
# Source this file: source "$(dirname "${BASH_SOURCE[0]}")/lib/audit-sanitize.sh"
|
|
14
|
+
# ============================================================================
|
|
15
|
+
|
|
16
|
+
# Millisecond epochs are 13 digits from 2001 through 2286; seconds epochs are
|
|
17
|
+
# 10 digits in that whole window. A 10-digit start is therefore provably the
|
|
18
|
+
# wrong unit, and anything else non-numeric is corruption. Both cases yield
|
|
19
|
+
# "null"; a valid 13-digit value passes through untouched.
|
|
20
|
+
sanitize_start() {
|
|
21
|
+
local s="$1"
|
|
22
|
+
case "$s" in
|
|
23
|
+
''|*[!0-9]*) printf 'null' ;;
|
|
24
|
+
*)
|
|
25
|
+
if [ "${#s}" -eq 13 ]; then
|
|
26
|
+
printf '%s' "$s"
|
|
27
|
+
else
|
|
28
|
+
printf 'null'
|
|
29
|
+
fi
|
|
30
|
+
;;
|
|
31
|
+
esac
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# compute_duration_ms <start_ms> <end_ms> -> delta | null
|
|
35
|
+
compute_duration_ms() {
|
|
36
|
+
local clean_end clean_start
|
|
37
|
+
clean_start=$(sanitize_start "$1")
|
|
38
|
+
clean_end=$(sanitize_start "$2")
|
|
39
|
+
if [ "$clean_start" = "null" ] || [ "$clean_end" = "null" ]; then
|
|
40
|
+
printf 'null'
|
|
41
|
+
return
|
|
42
|
+
fi
|
|
43
|
+
local delta=$((clean_end - clean_start))
|
|
44
|
+
if [ "$delta" -lt 0 ]; then
|
|
45
|
+
# Clock skew between two now_ms() calls is rarer than poisoning but the
|
|
46
|
+
# same rule applies: a negative runtime must never masquerade as 0.
|
|
47
|
+
printf 'null'
|
|
48
|
+
return
|
|
49
|
+
fi
|
|
50
|
+
printf '%s' "$delta"
|
|
51
|
+
}
|