@@ -216,6 +216,28 @@ fn check_json(path: string) -> string {
216216 return ""
217217}
218218
219+ // ─── Check R19: SILENT EXIT 금지 — exit(1) 전 stderr 출력 필수 + catch 로그 필수 ───
220+ // 탐지 2종:
221+ // (1) exit(1) 직전 5줄에 println/eprintln/>&2/stderr 없음
222+ // (2) catch 블록 body 에 print/e.* 참조 없음 (silent swallow)
223+ // 예외: 라인 주석 // @allow-silent-exit 또는 // @allow-silent-catch
224+ fn check_silent_exit(path: string) -> string {
225+ let e = ext_of(path)
226+ if e != ".hexa" && e != ".sh" && e != ".py" && e != ".ts" && e != ".js" { return "" }
227+ // 하네스 자신은 exec 폴백 의도적 silent — 스킵
228+ if path.starts_with("shared/harness/") { return "" }
229+ let full = REPO_ROOT + "/" + path
230+ // (1) exit(1) silent
231+ let awk1 = "awk '/exit[[:space:]]*\\([[:space:]]*1[[:space:]]*\\)/ && $0 !~ /@allow-silent-exit/ { has=0; for(i=NR-5;i<NR;i++){if(lines[i] ~ /println|eprintln|>&2|stderr|fprintf[[:space:]]*\\(stderr/) has=1}; if(!has) c++ } { lines[NR]=$0 } END{print c+0}'"
232+ let silent_exits = to_int(_str(exec(awk1 + " '" + full + "' 2>/dev/null || echo 0")).trim())
233+ // (2) silent catch — block 안에 print 또는 error var 참조 없음
234+ let awk2 = "awk '/catch[[:space:]]+[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*\\{/ && $0 !~ /@allow-silent-catch/ { start=NR; depth=0; buf=\"\"; for(i=1;i<=length($0);i++){c=substr($0,i,1); if(c==\"{\")depth++; else if(c==\"}\")depth--}; buf=$0; if(depth==0){ if(buf !~ /println|eprintln|>&2|stderr/) n++ ; next } in_b=1; next } in_b { buf=buf $0; for(i=1;i<=length($0);i++){c=substr($0,i,1); if(c==\"{\")depth++; else if(c==\"}\")depth--}; if(depth==0){ if(buf !~ /println|eprintln|>&2|stderr/) n++; in_b=0 } } END{print n+0}'"
235+ let silent_catches = to_int(_str(exec(awk2 + " '" + full + "' 2>/dev/null || echo 0")).trim())
236+ let total = silent_exits + silent_catches
237+ if total == 0 { return "" }
238+ return "R19|error|SILENT EXIT — exit(1) 앞 stderr 없음 " + to_string(silent_exits) + "건 + silent catch " + to_string(silent_catches) + "건 (// @allow-silent-exit/catch 로 예외)"
239+ }
240+
219241// ─── Check 9: harness 수정 가드 — commit 메시지 승인 마커 필수 ───
220242// shared/harness/ 아래 .hexa/.json/.jsonl/.md 수정 시 commit 메시지에
221243// "(harness):" prefix 또는 "harness-approved" 포함 필요.
@@ -402,6 +424,12 @@ for f in files {
402424 // check_harness_modification: shared/harness/ 수정 시 승인 마커 필요
403425 let v9 = check_harness_modification(path)
404426 if v9 != "" { violations = violations.push(path + "|" + v9) }
427+
428+ // check_silent_exit (R19): .hexa/.sh/.py/.ts/.js 대상
429+ if e == ".hexa" || e == ".sh" || e == ".py" || e == ".ts" || e == ".js" {
430+ let v10 = check_silent_exit(path)
431+ if v10 != "" { violations = violations.push(path + "|" + v10) }
432+ }
405433}
406434
407435let pass = len(violations) == 0
0 commit comments