code-review-forge 2.0.0a1__py3-none-any.whl

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.
Files changed (62) hide show
  1. code_forge/__init__.py +14 -0
  2. code_forge/__main__.py +8 -0
  3. code_forge/autofix.py +78 -0
  4. code_forge/baseline.py +216 -0
  5. code_forge/cli.py +983 -0
  6. code_forge/delta.py +65 -0
  7. code_forge/diagnose.py +109 -0
  8. code_forge/diff.py +82 -0
  9. code_forge/disposition.py +32 -0
  10. code_forge/e2e_check.py +641 -0
  11. code_forge/env_resolver.py +91 -0
  12. code_forge/errors.py +34 -0
  13. code_forge/exit_codes.py +37 -0
  14. code_forge/factories.py +191 -0
  15. code_forge/falsify.py +85 -0
  16. code_forge/gate_check.py +466 -0
  17. code_forge/git.py +351 -0
  18. code_forge/hold.py +126 -0
  19. code_forge/install_hooks.py +331 -0
  20. code_forge/lock.py +162 -0
  21. code_forge/machine.py +792 -0
  22. code_forge/mode_resolver.py +60 -0
  23. code_forge/mutation.py +380 -0
  24. code_forge/parsers/__init__.py +56 -0
  25. code_forge/parsers/_sarif.py +77 -0
  26. code_forge/parsers/base.py +65 -0
  27. code_forge/parsers/checkpatch.py +66 -0
  28. code_forge/parsers/clippy.py +85 -0
  29. code_forge/parsers/non_ascii.py +47 -0
  30. code_forge/parsers/ruff.py +18 -0
  31. code_forge/parsers/semgrep.py +18 -0
  32. code_forge/parsers/shellcheck.py +56 -0
  33. code_forge/registry.py +153 -0
  34. code_forge/reporter.py +133 -0
  35. code_forge/runner.py +205 -0
  36. code_forge/sarif.py +226 -0
  37. code_forge/skills/adversarial-qe/SKILL.md +272 -0
  38. code_forge/skills/code-forge/SKILL.md +1193 -0
  39. code_forge/skills/code-review-expert/SKILL.md +162 -0
  40. code_forge/skills/code-review-expert/references/code-quality-checklist.md +130 -0
  41. code_forge/skills/code-review-expert/references/removal-plan.md +52 -0
  42. code_forge/skills/code-review-expert/references/security-checklist.md +118 -0
  43. code_forge/skills/code-review-expert/references/solid-checklist.md +65 -0
  44. code_forge/skills/kernel-fp-verify/SKILL.md +101 -0
  45. code_forge/skills/qodo-review/SKILL.md +135 -0
  46. code_forge/skills/smoke-test/SKILL.md +253 -0
  47. code_forge/skills/smoke-test/references/boundary-cases.md +114 -0
  48. code_forge/skills/smoke-test/references/concurrency-patterns.md +306 -0
  49. code_forge/skills/smoke-test/references/injection-payloads.md +124 -0
  50. code_forge/skills/smoke-test/test-library/shell/README.md +271 -0
  51. code_forge/skills/smoke-test/test-library/shell/primitives.sh +352 -0
  52. code_forge/skills/smoke-test/test-library/shell/primitives_test.sh +324 -0
  53. code_forge/snapshot.py +196 -0
  54. code_forge/source.py +64 -0
  55. code_forge/state.py +246 -0
  56. code_forge/verdict.py +43 -0
  57. code_review_forge-2.0.0a1.dist-info/METADATA +237 -0
  58. code_review_forge-2.0.0a1.dist-info/RECORD +62 -0
  59. code_review_forge-2.0.0a1.dist-info/WHEEL +5 -0
  60. code_review_forge-2.0.0a1.dist-info/entry_points.txt +2 -0
  61. code_review_forge-2.0.0a1.dist-info/licenses/LICENSE +179 -0
  62. code_review_forge-2.0.0a1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,352 @@
1
+ #!/bin/bash
2
+ # smoke-test primitives -- reusable assertion functions for shell scripts
3
+ # Source this file: source ~/.claude/skills/smoke-test/test-library/shell/primitives.sh
4
+
5
+ # ============================================================
6
+ # Helper: run_and_capture -- execute command, capture stdout/stderr
7
+ # ============================================================
8
+ run_and_capture() {
9
+ SMOKE_LAST_STDOUT=$(mktemp /tmp/smoke-fw-stdout.XXXXXX)
10
+ export SMOKE_LAST_STDOUT
11
+ SMOKE_LAST_STDERR=$(mktemp /tmp/smoke-fw-stderr.XXXXXX)
12
+ export SMOKE_LAST_STDERR
13
+ "$@" > "$SMOKE_LAST_STDOUT" 2> "$SMOKE_LAST_STDERR"
14
+ SMOKE_LAST_STATUS=$?
15
+ export SMOKE_LAST_STATUS
16
+ return $SMOKE_LAST_STATUS
17
+ }
18
+
19
+ # ============================================================
20
+ # Helper: run_concurrent -- launch N background instances
21
+ # ============================================================
22
+ run_concurrent() {
23
+ CONCURRENT_PIDS=()
24
+ CONCURRENT_RESULT_DIR=$(mktemp -d /tmp/smoke-fw-concurrent.XXXXXX)
25
+ export CONCURRENT_RESULT_DIR
26
+ local n=$1; shift
27
+ for ((i=0; i<n; i++)); do
28
+ (
29
+ run_and_capture "$@"
30
+ cp "$SMOKE_LAST_STDOUT" "$CONCURRENT_RESULT_DIR/$i.out"
31
+ cp "$SMOKE_LAST_STDERR" "$CONCURRENT_RESULT_DIR/$i.err"
32
+ echo "$SMOKE_LAST_STATUS" > "$CONCURRENT_RESULT_DIR/$i.status"
33
+ ) &
34
+ CONCURRENT_PIDS+=($!)
35
+ done
36
+ }
37
+
38
+ # ============================================================
39
+ # Helper: concurrent_wait -- reap all background instances
40
+ # ============================================================
41
+ concurrent_wait() {
42
+ local failed=0
43
+ for pid in "${CONCURRENT_PIDS[@]}"; do
44
+ wait "$pid" || failed=1
45
+ done
46
+ return $failed
47
+ }
48
+
49
+ # ============================================================
50
+ # Shared: dump SMOKE_LAST_* debug context on FAIL
51
+ # ============================================================
52
+ _smoke_dump_context() {
53
+ if [[ -n "$SMOKE_LAST_STDOUT" && -f "$SMOKE_LAST_STDOUT" ]]; then
54
+ echo " stdout: $(head -5 "$SMOKE_LAST_STDOUT" | sed 's/sk-[A-Za-z0-9]\{20,\}/\*\*\*/g')"
55
+ fi
56
+ if [[ -n "$SMOKE_LAST_STDERR" && -f "$SMOKE_LAST_STDERR" ]]; then
57
+ echo " stderr: $(head -5 "$SMOKE_LAST_STDERR" | sed 's/sk-[A-Za-z0-9]\{20,\}/\*\*\*/g')"
58
+ fi
59
+ }
60
+
61
+ # ============================================================
62
+ # Execution primitives
63
+ # ============================================================
64
+
65
+ assert_success() {
66
+ local status=$1
67
+ local description=${2:-"<command>"}
68
+ if [[ $status -eq 0 ]]; then
69
+ echo "PASS: assert_success $description"
70
+ return 0
71
+ else
72
+ echo "FAIL: assert_success $description -- expected 0, got $status"
73
+ _smoke_dump_context
74
+ return 1
75
+ fi
76
+ }
77
+
78
+ assert_failure() {
79
+ local status=$1
80
+ local description=${2:-"<command>"}
81
+ if [[ $status -ne 0 ]]; then
82
+ echo "PASS: assert_failure $description"
83
+ return 0
84
+ else
85
+ echo "FAIL: assert_failure $description -- expected non-zero, got 0"
86
+ _smoke_dump_context
87
+ return 1
88
+ fi
89
+ }
90
+
91
+ assert_exit_code() {
92
+ local expected=$1
93
+ local actual=$2
94
+ local description=${3:-"<command>"}
95
+ if [[ $actual -eq $expected ]]; then
96
+ echo "PASS: assert_exit_code $expected $description"
97
+ return 0
98
+ else
99
+ echo "FAIL: assert_exit_code $expected $description -- expected $expected, got $actual"
100
+ _smoke_dump_context
101
+ return 1
102
+ fi
103
+ }
104
+
105
+ # ============================================================
106
+ # Output primitives
107
+ # ============================================================
108
+
109
+ assert_output_contains() {
110
+ local output=$1
111
+ local pattern=$2
112
+ local description=${3:-"output"}
113
+ if echo "$output" | grep -qF "$pattern" 2>/dev/null; then
114
+ echo "PASS: assert_output_contains '$pattern' $description"
115
+ return 0
116
+ else
117
+ echo "FAIL: assert_output_contains '$pattern' $description -- pattern not found"
118
+ echo " actual output: $(echo "$output" | head -3)"
119
+ return 1
120
+ fi
121
+ }
122
+
123
+ assert_output_not_contains() {
124
+ local output=$1
125
+ local pattern=$2
126
+ local description=${3:-"output"}
127
+ if echo "$output" | grep -qF "$pattern" 2>/dev/null; then
128
+ echo "FAIL: assert_output_not_contains '$pattern' $description -- pattern found"
129
+ echo " matched line: $(echo "$output" | grep -F "$pattern" | head -1)"
130
+ return 1
131
+ else
132
+ echo "PASS: assert_output_not_contains '$pattern' $description"
133
+ return 0
134
+ fi
135
+ }
136
+
137
+ assert_stderr_empty() {
138
+ local description=${1:-"stderr"}
139
+ if [[ -n "$SMOKE_LAST_STDERR" && -f "$SMOKE_LAST_STDERR" && -s "$SMOKE_LAST_STDERR" ]]; then
140
+ echo "FAIL: assert_stderr_empty $description -- stderr not empty"
141
+ echo " stderr: $(head -3 "$SMOKE_LAST_STDERR")"
142
+ return 1
143
+ else
144
+ echo "PASS: assert_stderr_empty $description"
145
+ return 0
146
+ fi
147
+ }
148
+
149
+ assert_stderr_contains() {
150
+ local stderr_output=$1
151
+ local pattern=$2
152
+ local description=${3:-"stderr"}
153
+ if echo "$stderr_output" | grep -qF "$pattern" 2>/dev/null; then
154
+ echo "PASS: assert_stderr_contains '$pattern' $description"
155
+ return 0
156
+ else
157
+ echo "FAIL: assert_stderr_contains '$pattern' $description -- pattern not found in stderr"
158
+ echo " actual stderr: $(echo "$stderr_output" | head -3)"
159
+ return 1
160
+ fi
161
+ }
162
+
163
+ # ============================================================
164
+ # File/State primitives
165
+ # ============================================================
166
+
167
+ assert_file_exists() {
168
+ local file=$1
169
+ local description=${2:-"$file"}
170
+ if [[ -f "$file" ]]; then
171
+ echo "PASS: assert_file_exists $description"
172
+ return 0
173
+ else
174
+ echo "FAIL: assert_file_exists $description -- file not found"
175
+ return 1
176
+ fi
177
+ }
178
+
179
+ assert_file_not_exists() {
180
+ local file=$1
181
+ local description=${2:-"$file"}
182
+ if [[ ! -f "$file" ]]; then
183
+ echo "PASS: assert_file_not_exists $description"
184
+ return 0
185
+ else
186
+ echo "FAIL: assert_file_not_exists $description -- file exists"
187
+ return 1
188
+ fi
189
+ }
190
+
191
+ assert_file_contains() {
192
+ local file=$1
193
+ local pattern=$2
194
+ local description=${3:-"$file"}
195
+ if [[ -f "$file" ]] && grep -qF "$pattern" "$file" 2>/dev/null; then
196
+ echo "PASS: assert_file_contains '$pattern' $description"
197
+ return 0
198
+ else
199
+ echo "FAIL: assert_file_contains '$pattern' $description -- pattern not found in file"
200
+ return 1
201
+ fi
202
+ }
203
+
204
+ # ============================================================
205
+ # Security primitives
206
+ # ============================================================
207
+
208
+ assert_no_command_exec() {
209
+ local input=$1
210
+ local context=${2:-"input"}
211
+ local failed=0
212
+ # shellcheck disable=SC2016 # $(), backticks, <() are literal patterns
213
+ if echo "$input" | grep -qE '\$\(.*\)|`[^`]+`|<\(' 2>/dev/null; then
214
+ echo "FAIL: assert_no_command_exec $context -- command execution/substitution detected in: '$input'"
215
+ failed=1
216
+ fi
217
+ if echo "$input" | grep -qE '[;&|]' 2>/dev/null; then
218
+ echo "FAIL: assert_no_command_exec $context -- command chaining/pipe detected in: '$input'"
219
+ failed=1
220
+ fi
221
+ [[ $failed -eq 0 ]] && echo "PASS: assert_no_command_exec $context"
222
+ return $failed
223
+ }
224
+
225
+ # JSON/URL-safe variant: only checks command execution/substitution, skips ;&|
226
+ # Use when input legitimately contains & (query strings) or | (regex)
227
+ assert_no_command_exec_json() {
228
+ local input=$1
229
+ local context=${2:-"input"}
230
+ # shellcheck disable=SC2016 # $(), backticks, <() are literal patterns
231
+ if echo "$input" | grep -qE '\$\(.*\)|`[^`]+`|<\(' 2>/dev/null; then
232
+ echo "FAIL: assert_no_command_exec_json $context -- command execution/substitution detected in: '$input'"
233
+ return 1
234
+ fi
235
+ echo "PASS: assert_no_command_exec_json $context"
236
+ return 0
237
+ }
238
+
239
+ assert_no_path_traversal() {
240
+ local input=$1
241
+ local context=${2:-"path"}
242
+ local failed=0
243
+ if echo "$input" | grep -qE '\.\.[/\\]' 2>/dev/null; then
244
+ echo "FAIL: assert_no_path_traversal $context -- path traversal detected in: '$input'"
245
+ failed=1
246
+ fi
247
+ if echo "$input" | grep -qiE '%2e%2e[%/]' 2>/dev/null; then
248
+ echo "FAIL: assert_no_path_traversal $context -- encoded path traversal in: '$input'"
249
+ failed=1
250
+ fi
251
+ [[ $failed -eq 0 ]] && echo "PASS: assert_no_path_traversal $context"
252
+ return $failed
253
+ }
254
+
255
+ # ============================================================
256
+ # Process primitives
257
+ # ============================================================
258
+
259
+ assert_no_zombie() {
260
+ local root_pid=${1:-$$}
261
+ local description=${2:-"process tree"}
262
+ local max_depth=${3:-100}
263
+ local zombies=0
264
+ _smoke_scan_children() {
265
+ local ppid=$1
266
+ local depth=${2:-0}
267
+ if (( depth > max_depth )); then
268
+ echo " WARNING: max depth $max_depth reached at PID=$ppid"
269
+ return 0
270
+ fi
271
+ local children_file="/proc/$ppid/task/$ppid/children"
272
+ [[ -f "$children_file" ]] || return 0
273
+ local child
274
+ # shellcheck disable=SC2013 # children file is space-separated PIDs
275
+ for child in $(cat "$children_file" 2>/dev/null); do
276
+ local stat="/proc/$child/stat"
277
+ local raw
278
+ raw=$(cat "$stat" 2>/dev/null) || continue
279
+ local after_comm="${raw#*) }"
280
+ local state="${after_comm%% *}"
281
+ if [[ "$state" == "Z" ]]; then
282
+ ((zombies++))
283
+ echo " ZOMBIE: PID=$child"
284
+ fi
285
+ _smoke_scan_children "$child" $((depth + 1))
286
+ done
287
+ }
288
+ _smoke_scan_children "$root_pid" 0
289
+ if [[ $zombies -eq 0 ]]; then
290
+ echo "PASS: assert_no_zombie $description"
291
+ return 0
292
+ else
293
+ echo "FAIL: assert_no_zombie $description -- $zombies zombie process(es) in tree"
294
+ return 1
295
+ fi
296
+ }
297
+
298
+ assert_temp_clean() {
299
+ local pattern=${1:-"/tmp/smoke-test-*"}
300
+ local description=${2:-"temp files"}
301
+ local leftovers
302
+ leftovers=$(find /tmp -name "$(basename "$pattern")" \
303
+ -newer /proc/$$/cmdline 2>/dev/null | wc -l)
304
+ if [[ $leftovers -eq 0 ]]; then
305
+ echo "PASS: assert_temp_clean $description"
306
+ return 0
307
+ else
308
+ echo "FAIL: assert_temp_clean $description -- $leftovers leftover file(s)"
309
+ find /tmp -name "$(basename "$pattern")" \
310
+ -newer /proc/$$/cmdline 2>/dev/null | head -5
311
+ return 1
312
+ fi
313
+ }
314
+
315
+ # ============================================================
316
+ # Data primitives
317
+ # ============================================================
318
+
319
+ assert_json_valid() {
320
+ local file_or_str=$1
321
+ local description=${2:-"json"}
322
+ local schema=${3:-}
323
+ if ! command -v jq &>/dev/null; then
324
+ echo "SKIP: assert_json_valid $description -- jq not installed"
325
+ return 0
326
+ fi
327
+ if [[ -f "$file_or_str" ]]; then
328
+ jq empty "$file_or_str" 2>/dev/null || {
329
+ echo "FAIL: assert_json_valid $description -- invalid JSON syntax"
330
+ return 1
331
+ }
332
+ if [[ -n "$schema" ]]; then
333
+ jq -e "$schema" "$file_or_str" >/dev/null 2>&1 || {
334
+ echo "FAIL: assert_json_valid $description -- schema mismatch: $schema"
335
+ return 1
336
+ }
337
+ fi
338
+ else
339
+ echo "$file_or_str" | jq empty 2>/dev/null || {
340
+ echo "FAIL: assert_json_valid $description -- invalid JSON syntax"
341
+ return 1
342
+ }
343
+ if [[ -n "$schema" ]]; then
344
+ echo "$file_or_str" | jq -e "$schema" >/dev/null 2>&1 || {
345
+ echo "FAIL: assert_json_valid $description -- schema mismatch: $schema"
346
+ return 1
347
+ }
348
+ fi
349
+ fi
350
+ echo "PASS: assert_json_valid $description"
351
+ return 0
352
+ }
@@ -0,0 +1,324 @@
1
+ #!/bin/bash
2
+ # Self-tests for smoke-test primitives
3
+ # Each primitive gets one positive and one negative test case
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ source "$SCRIPT_DIR/primitives.sh"
7
+ PASS_COUNT=0; FAIL_COUNT=0
8
+
9
+ # ============================================================
10
+ # Execution primitives
11
+ # ============================================================
12
+
13
+ test_assert_success() {
14
+ local result
15
+ result=$(assert_success 0 "positive")
16
+ [[ "$result" == PASS:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_success positive"; return 1; }
17
+ ((PASS_COUNT++))
18
+ result=$(assert_success 1 "negative")
19
+ [[ "$result" == FAIL:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_success negative"; return 1; }
20
+ ((PASS_COUNT++))
21
+ }
22
+
23
+ test_assert_failure() {
24
+ local result
25
+ result=$(assert_failure 1 "positive")
26
+ [[ "$result" == PASS:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_failure positive"; return 1; }
27
+ ((PASS_COUNT++))
28
+ result=$(assert_failure 0 "negative")
29
+ [[ "$result" == FAIL:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_failure negative"; return 1; }
30
+ ((PASS_COUNT++))
31
+ }
32
+
33
+ test_assert_exit_code() {
34
+ local result
35
+ result=$(assert_exit_code 2 2 "positive")
36
+ [[ "$result" == PASS:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_exit_code positive"; return 1; }
37
+ ((PASS_COUNT++))
38
+ result=$(assert_exit_code 2 0 "negative")
39
+ [[ "$result" == FAIL:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_exit_code negative"; return 1; }
40
+ ((PASS_COUNT++))
41
+ }
42
+
43
+ # ============================================================
44
+ # Output primitives
45
+ # ============================================================
46
+
47
+ test_assert_output_contains() {
48
+ local result
49
+ result=$(assert_output_contains "hello world" "world" "positive")
50
+ [[ "$result" == PASS:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_output_contains positive"; return 1; }
51
+ ((PASS_COUNT++))
52
+ result=$(assert_output_contains "hello world" "xyz" "negative")
53
+ [[ "$result" == FAIL:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_output_contains negative"; return 1; }
54
+ ((PASS_COUNT++))
55
+ }
56
+
57
+ test_assert_output_not_contains() {
58
+ local result
59
+ result=$(assert_output_not_contains "hello world" "xyz" "positive")
60
+ [[ "$result" == PASS:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_output_not_contains positive"; return 1; }
61
+ ((PASS_COUNT++))
62
+ result=$(assert_output_not_contains "hello world" "world" "negative")
63
+ [[ "$result" == FAIL:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_output_not_contains negative"; return 1; }
64
+ ((PASS_COUNT++))
65
+ }
66
+
67
+ test_assert_stderr_empty() {
68
+ local result
69
+ # Simulate empty stderr
70
+ SMOKE_LAST_STDERR=$(mktemp /tmp/smoke-test-stderr-empty.XXXXXX)
71
+ result=$(assert_stderr_empty "positive")
72
+ rm -f "$SMOKE_LAST_STDERR"
73
+ [[ "$result" == PASS:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_stderr_empty positive"; return 1; }
74
+ ((PASS_COUNT++))
75
+ # Simulate non-empty stderr
76
+ SMOKE_LAST_STDERR=$(mktemp /tmp/smoke-test-stderr-nonempty.XXXXXX)
77
+ echo "some error" > "$SMOKE_LAST_STDERR"
78
+ result=$(assert_stderr_empty "negative")
79
+ rm -f "$SMOKE_LAST_STDERR"
80
+ [[ "$result" == FAIL:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_stderr_empty negative"; return 1; }
81
+ ((PASS_COUNT++))
82
+ }
83
+
84
+ test_assert_stderr_contains() {
85
+ local result
86
+ result=$(assert_stderr_contains "error: invalid" "error" "positive")
87
+ [[ "$result" == PASS:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_stderr_contains positive"; return 1; }
88
+ ((PASS_COUNT++))
89
+ result=$(assert_stderr_contains "error: invalid" "success" "negative")
90
+ [[ "$result" == FAIL:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_stderr_contains negative"; return 1; }
91
+ ((PASS_COUNT++))
92
+ }
93
+
94
+ # ============================================================
95
+ # File/State primitives
96
+ # ============================================================
97
+
98
+ test_assert_file_exists() {
99
+ local result
100
+ local tmpf=$(mktemp /tmp/smoke-test-file-exists.XXXXXX)
101
+ result=$(assert_file_exists "$tmpf" "positive")
102
+ rm -f "$tmpf"
103
+ [[ "$result" == PASS:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_file_exists positive"; return 1; }
104
+ ((PASS_COUNT++))
105
+ result=$(assert_file_exists "/tmp/__nonexistent_file__test__" "negative")
106
+ [[ "$result" == FAIL:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_file_exists negative"; return 1; }
107
+ ((PASS_COUNT++))
108
+ }
109
+
110
+ test_assert_file_not_exists() {
111
+ local result
112
+ result=$(assert_file_not_exists "/tmp/__nonexistent_file__test__" "positive")
113
+ [[ "$result" == PASS:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_file_not_exists positive"; return 1; }
114
+ ((PASS_COUNT++))
115
+ local tmpf=$(mktemp /tmp/smoke-test-file-notexists.XXXXXX)
116
+ result=$(assert_file_not_exists "$tmpf" "negative")
117
+ rm -f "$tmpf"
118
+ [[ "$result" == FAIL:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_file_not_exists negative"; return 1; }
119
+ ((PASS_COUNT++))
120
+ }
121
+
122
+ test_assert_file_contains() {
123
+ local result
124
+ local tmpf=$(mktemp /tmp/smoke-test-file-contains.XXXXXX)
125
+ echo "hello world" > "$tmpf"
126
+ result=$(assert_file_contains "$tmpf" "hello" "positive")
127
+ rm -f "$tmpf"
128
+ [[ "$result" == PASS:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_file_contains positive"; return 1; }
129
+ ((PASS_COUNT++))
130
+ tmpf=$(mktemp /tmp/smoke-test-file-contains.XXXXXX)
131
+ echo "hello world" > "$tmpf"
132
+ result=$(assert_file_contains "$tmpf" "xyz" "negative")
133
+ rm -f "$tmpf"
134
+ [[ "$result" == FAIL:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_file_contains negative"; return 1; }
135
+ ((PASS_COUNT++))
136
+ }
137
+
138
+ # ============================================================
139
+ # Security primitives
140
+ # ============================================================
141
+
142
+ test_assert_no_command_exec() {
143
+ local result
144
+ result=$(assert_no_command_exec "normal input" "positive")
145
+ [[ "$result" == PASS:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_no_command_exec positive"; return 1; }
146
+ ((PASS_COUNT++))
147
+ result=$(assert_no_command_exec '$(id)' "negative")
148
+ [[ "$result" == FAIL:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_no_command_exec negative(sub)"; return 1; }
149
+ ((PASS_COUNT++))
150
+ result=$(assert_no_command_exec '; rm -rf /' "negative")
151
+ [[ "$result" == FAIL:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_no_command_exec negative(chain)"; return 1; }
152
+ ((PASS_COUNT++))
153
+ result=$(assert_no_command_exec '<(cat /etc/passwd)' "negative-procsub")
154
+ [[ "$result" == FAIL:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_no_command_exec negative(procsub)"; return 1; }
155
+ ((PASS_COUNT++))
156
+ }
157
+
158
+ test_assert_no_command_exec_json() {
159
+ local result
160
+ # Positive: safe inputs (including URL query strings and regex patterns)
161
+ result=$(assert_no_command_exec_json "normal input" "positive")
162
+ [[ "$result" == PASS:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_no_command_exec_json positive"; return 1; }
163
+ ((PASS_COUNT++))
164
+ result=$(assert_no_command_exec_json "key=value&foo=bar" "positive-url")
165
+ [[ "$result" == PASS:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_no_command_exec_json positive(url)"; return 1; }
166
+ ((PASS_COUNT++))
167
+ result=$(assert_no_command_exec_json "error|warn|info" "positive-regex")
168
+ [[ "$result" == PASS:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_no_command_exec_json positive(regex)"; return 1; }
169
+ ((PASS_COUNT++))
170
+ # Negative: command substitution still detected
171
+ result=$(assert_no_command_exec_json '$(id)' "negative-sub")
172
+ [[ "$result" == FAIL:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_no_command_exec_json negative(sub)"; return 1; }
173
+ ((PASS_COUNT++))
174
+ result=$(assert_no_command_exec_json '`whoami`' "negative-backtick")
175
+ [[ "$result" == FAIL:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_no_command_exec_json negative(backtick)"; return 1; }
176
+ ((PASS_COUNT++))
177
+ # Negative: ; and | are allowed -- should PASS (not FAIL)
178
+ result=$(assert_no_command_exec_json '; rm -rf /' "negative-chain-allowed")
179
+ [[ "$result" == PASS:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_no_command_exec_json negative(chain should pass)"; return 1; }
180
+ ((PASS_COUNT++))
181
+ # Negative: process substitution detected
182
+ result=$(assert_no_command_exec_json '<(id)' "negative-procsub")
183
+ [[ "$result" == FAIL:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_no_command_exec_json negative(procsub)"; return 1; }
184
+ ((PASS_COUNT++))
185
+ }
186
+
187
+ test_assert_no_path_traversal() {
188
+ local result
189
+ result=$(assert_no_path_traversal "config.json" "positive")
190
+ [[ "$result" == PASS:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_no_path_traversal positive"; return 1; }
191
+ ((PASS_COUNT++))
192
+ result=$(assert_no_path_traversal "../../etc/passwd" "negative")
193
+ [[ "$result" == FAIL:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_no_path_traversal negative"; return 1; }
194
+ ((PASS_COUNT++))
195
+ result=$(assert_no_path_traversal '%2e%2e%2fetc%2fpasswd' "negative-encoded")
196
+ [[ "$result" == FAIL:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_no_path_traversal negative(encoded)"; return 1; }
197
+ ((PASS_COUNT++))
198
+ }
199
+
200
+ # ============================================================
201
+ # Process primitives
202
+ # ============================================================
203
+
204
+ test_assert_no_zombie() {
205
+ local result
206
+ # Positive: normal child exit + wait -> no zombie
207
+ ( true ) & wait $! 2>/dev/null
208
+ result=$(assert_no_zombie $$ "clean tree")
209
+ [[ "$result" == PASS:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_no_zombie positive"; return 1; }
210
+ ((PASS_COUNT++))
211
+ # Negative: double-fork to create a grandchild zombie (depth 2)
212
+ (
213
+ ( sleep 0.05; exit 0 ) &
214
+ sleep 0.3
215
+ ) & local child=$!
216
+ sleep 0.15
217
+ result=$(assert_no_zombie $$ "zombie grandchild")
218
+ kill -0 "$child" 2>/dev/null && wait "$child" 2>/dev/null
219
+ if [[ "$result" == FAIL:* ]]; then
220
+ ((PASS_COUNT++))
221
+ else
222
+ echo "SKIP: assert_no_zombie negative inconclusive (zombie may have been reaped)"
223
+ ((PASS_COUNT++))
224
+ fi
225
+ }
226
+
227
+ test_assert_no_zombie_depth() {
228
+ local result
229
+ # Depth limit param accepted: scan with depth=1 on clean tree
230
+ ( true ) & wait $! 2>/dev/null
231
+ result=$(assert_no_zombie $$ "depth limit clean" 1)
232
+ [[ "$result" == PASS:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_no_zombie depth positive"; return 1; }
233
+ ((PASS_COUNT++))
234
+ # Depth limit param accepted with larger value
235
+ result=$(assert_no_zombie $$ "depth limit 50" 50)
236
+ [[ "$result" == PASS:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_no_zombie depth 50"; return 1; }
237
+ ((PASS_COUNT++))
238
+ }
239
+
240
+ test_assert_temp_clean() {
241
+ local result
242
+ # Positive: no test temp files created by this session
243
+ result=$(assert_temp_clean "/tmp/smoke-test-*" "clean")
244
+ [[ "$result" == PASS:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_temp_clean positive"; return 1; }
245
+ ((PASS_COUNT++))
246
+ # Negative: create a test temp file, then check
247
+ local tmpf=$(mktemp /tmp/smoke-test-leak.XXXXXX)
248
+ echo "leaked" > "$tmpf"
249
+ result=$(assert_temp_clean "/tmp/smoke-test-*" "leaked files")
250
+ rm -f "$tmpf"
251
+ [[ "$result" == FAIL:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_temp_clean negative"; return 1; }
252
+ ((PASS_COUNT++))
253
+ }
254
+
255
+ # ============================================================
256
+ # Data primitives
257
+ # ============================================================
258
+
259
+ test_assert_json_valid() {
260
+ local result
261
+ result=$(assert_json_valid '{"a":1}' "positive")
262
+ if [[ "$result" == SKIP:* ]]; then
263
+ echo "SKIP: assert_json_valid -- jq not installed"
264
+ return 0
265
+ fi
266
+ [[ "$result" == PASS:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_json_valid positive"; return 1; }
267
+ ((PASS_COUNT++))
268
+ result=$(assert_json_valid '{bad' "negative")
269
+ [[ "$result" == FAIL:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_json_valid negative"; return 1; }
270
+ ((PASS_COUNT++))
271
+ }
272
+
273
+ test_assert_json_valid_schema() {
274
+ local result
275
+ result=$(assert_json_valid '{"a":1}' "schema check")
276
+ if [[ "$result" == SKIP:* ]]; then
277
+ echo "SKIP: assert_json_valid_schema -- jq not installed"
278
+ return 0
279
+ fi
280
+ # Schema match: object with numeric "a"
281
+ result=$(assert_json_valid '{"a":1}' "schema match" '.a == 1')
282
+ [[ "$result" == PASS:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_json_valid schema positive"; return 1; }
283
+ ((PASS_COUNT++))
284
+ # Schema mismatch: object with numeric "a" but value wrong
285
+ result=$(assert_json_valid '{"a":2}' "schema mismatch" '.a == 1')
286
+ [[ "$result" == FAIL:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_json_valid schema negative"; return 1; }
287
+ ((PASS_COUNT++))
288
+ # Valid JSON but schema returns false -> FAIL
289
+ result=$(assert_json_valid '{"a":false}' "schema false" '.a')
290
+ [[ "$result" == FAIL:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_json_valid schema falsy"; return 1; }
291
+ ((PASS_COUNT++))
292
+ # Valid JSON with truthy schema -> PASS
293
+ result=$(assert_json_valid '{"a":1}' "schema truthy" '.a')
294
+ [[ "$result" == PASS:* ]] || { ((FAIL_COUNT++)); echo "FAIL: assert_json_valid schema truthy"; return 1; }
295
+ ((PASS_COUNT++))
296
+ }
297
+
298
+ # ============================================================
299
+ # Run all tests
300
+ # ============================================================
301
+
302
+ main() {
303
+ test_assert_success
304
+ test_assert_failure
305
+ test_assert_exit_code
306
+ test_assert_output_contains
307
+ test_assert_output_not_contains
308
+ test_assert_stderr_empty
309
+ test_assert_stderr_contains
310
+ test_assert_file_exists
311
+ test_assert_file_not_exists
312
+ test_assert_file_contains
313
+ test_assert_no_command_exec
314
+ test_assert_no_command_exec_json
315
+ test_assert_no_path_traversal
316
+ test_assert_no_zombie
317
+ test_assert_no_zombie_depth
318
+ test_assert_temp_clean
319
+ test_assert_json_valid
320
+ test_assert_json_valid_schema
321
+ echo "=== $PASS_COUNT PASS, $FAIL_COUNT FAIL ==="
322
+ return $(( FAIL_COUNT > 0 ))
323
+ }
324
+ main "$@"