Merge pull request 'process: prevent newline-escaped Gitea comments via helper + guard (#372)' (#378) from feature/issue-372-comment-newline-guard into feature/v3-session-policy-stream
All checks were successful
Gitea CI / test (push) Successful in 31s
All checks were successful
Gitea CI / test (push) Successful in 31s
Reviewed-on: #378
This commit was merged in pull request #378.
This commit is contained in:
@@ -38,6 +38,27 @@ python3 scripts/validate_docs_sync.py
|
||||
|
||||
### tea CLI (Gitea Command Line Tool)
|
||||
|
||||
#### ❌ Comment Newline Escaping (`\n` rendered literally)
|
||||
```bash
|
||||
YES="" ~/bin/tea comment 374 "line1\nline2"
|
||||
# Web UI shows "\n" as text instead of line breaks
|
||||
```
|
||||
**💡 Reason:** Inline string escaping is interpreted literally before comment submission.
|
||||
|
||||
**✅ Solution:** Use file-based helper to preserve multiline text
|
||||
```bash
|
||||
cat > /tmp/comment.md <<'EOF'
|
||||
line1
|
||||
line2
|
||||
EOF
|
||||
|
||||
scripts/tea_comment.sh 374 /tmp/comment.md
|
||||
```
|
||||
|
||||
**📝 Notes:**
|
||||
- `scripts/tea_comment.sh` accepts stdin with `-` as body source.
|
||||
- The helper fails fast when body looks like escaped-newline text only.
|
||||
|
||||
#### ❌ TTY Error - Interactive Confirmation Fails
|
||||
```bash
|
||||
~/bin/tea issues create --repo X --title "Y" --description "Z"
|
||||
|
||||
@@ -70,6 +70,22 @@ Gitea 이슈/PR/코멘트 작업 전에 모든 에이전트는 아래를 먼저
|
||||
|
||||
Issue/PR 본문 작성 시 줄바꿈(`\n`)이 문자열 그대로 저장되는 문제가 반복될 수 있다. 원인은 `-d "...\n..."` 형태에서 쉘/CLI가 이스케이프를 실제 개행으로 해석하지 않기 때문이다.
|
||||
|
||||
코멘트도 동일한 문제가 자주 발생하므로, 코멘트는 파일 기반 래퍼를 표준으로 사용한다.
|
||||
|
||||
```bash
|
||||
# 권장: 파일/STDIN 기반 코멘트 등록 (줄바꿈 보존)
|
||||
cat > /tmp/review.md <<'EOF'
|
||||
리뷰 반영 완료했습니다.
|
||||
|
||||
- 항목 1
|
||||
- 항목 2
|
||||
EOF
|
||||
|
||||
scripts/tea_comment.sh 374 /tmp/review.md
|
||||
# 또는
|
||||
cat /tmp/review.md | scripts/tea_comment.sh 374 -
|
||||
```
|
||||
|
||||
권장 패턴:
|
||||
|
||||
```bash
|
||||
|
||||
49
scripts/tea_comment.sh
Executable file
49
scripts/tea_comment.sh
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
# Safe helper for posting multiline Gitea comments without escaped-newline artifacts.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ] || [ "$#" -lt 2 ]; then
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
scripts/tea_comment.sh <issue_or_pr_index> <body_file|-> [repo]
|
||||
|
||||
Examples:
|
||||
scripts/tea_comment.sh 374 /tmp/comment.md
|
||||
cat /tmp/comment.md | scripts/tea_comment.sh 374 - jihoson/The-Ouroboros
|
||||
|
||||
Notes:
|
||||
- Use file/stdin input to preserve real newlines.
|
||||
- Passing inline strings with "\n" is intentionally avoided by this helper.
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
INDEX="$1"
|
||||
BODY_SOURCE="$2"
|
||||
REPO="${3:-jihoson/The-Ouroboros}"
|
||||
|
||||
if [ "$BODY_SOURCE" = "-" ]; then
|
||||
BODY="$(cat)"
|
||||
else
|
||||
if [ ! -f "$BODY_SOURCE" ]; then
|
||||
echo "[FAIL] body file not found: $BODY_SOURCE" >&2
|
||||
exit 1
|
||||
fi
|
||||
BODY="$(cat "$BODY_SOURCE")"
|
||||
fi
|
||||
|
||||
if [ -z "$BODY" ]; then
|
||||
echo "[FAIL] empty comment body" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Guard against the common escaped-newline mistake.
|
||||
if [[ "$BODY" == *"\\n"* ]] && [[ "$BODY" != *$'\n'* ]]; then
|
||||
echo "[FAIL] body appears to contain escaped newlines (\\n) instead of real line breaks" >&2
|
||||
echo "Use a multiline file/heredoc and pass that file to scripts/tea_comment.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
YES="" ~/bin/tea comment "$INDEX" --repo "$REPO" "$BODY"
|
||||
|
||||
@@ -215,6 +215,7 @@ def main() -> int:
|
||||
[
|
||||
"Session Handover Gate (Mandatory)",
|
||||
"session_handover_check.py --strict",
|
||||
"scripts/tea_comment.sh",
|
||||
],
|
||||
errors,
|
||||
)
|
||||
@@ -223,6 +224,8 @@ def main() -> int:
|
||||
[
|
||||
"Session Handover Preflight (Mandatory)",
|
||||
"session_handover_check.py --strict",
|
||||
"Comment Newline Escaping",
|
||||
"scripts/tea_comment.sh",
|
||||
],
|
||||
errors,
|
||||
)
|
||||
|
||||
@@ -187,3 +187,100 @@ def test_validate_read_only_approval_skips_when_no_readonly_file_changed() -> No
|
||||
module.validate_read_only_approval(changed_files, errors, warnings)
|
||||
assert errors == []
|
||||
assert warnings == []
|
||||
|
||||
|
||||
def test_must_contain_enforces_workflow_newline_helper_tokens(tmp_path) -> None:
|
||||
module = _load_module()
|
||||
workflow_doc = tmp_path / "workflow.md"
|
||||
workflow_doc.write_text(
|
||||
"\n".join(
|
||||
[
|
||||
"Session Handover Gate (Mandatory)",
|
||||
"python3 scripts/session_handover_check.py --strict",
|
||||
"scripts/tea_comment.sh",
|
||||
]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
errors: list[str] = []
|
||||
module.must_contain(
|
||||
workflow_doc,
|
||||
[
|
||||
"Session Handover Gate (Mandatory)",
|
||||
"session_handover_check.py --strict",
|
||||
"scripts/tea_comment.sh",
|
||||
],
|
||||
errors,
|
||||
)
|
||||
assert errors == []
|
||||
|
||||
|
||||
def test_must_contain_fails_when_workflow_missing_newline_helper_token(tmp_path) -> None:
|
||||
module = _load_module()
|
||||
workflow_doc = tmp_path / "workflow.md"
|
||||
workflow_doc.write_text(
|
||||
"\n".join(
|
||||
[
|
||||
"Session Handover Gate (Mandatory)",
|
||||
"python3 scripts/session_handover_check.py --strict",
|
||||
]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
errors: list[str] = []
|
||||
module.must_contain(
|
||||
workflow_doc,
|
||||
["scripts/tea_comment.sh"],
|
||||
errors,
|
||||
)
|
||||
assert any("scripts/tea_comment.sh" in err for err in errors)
|
||||
|
||||
|
||||
def test_must_contain_enforces_commands_newline_section_tokens(tmp_path) -> None:
|
||||
module = _load_module()
|
||||
commands_doc = tmp_path / "commands.md"
|
||||
commands_doc.write_text(
|
||||
"\n".join(
|
||||
[
|
||||
"Session Handover Preflight (Mandatory)",
|
||||
"python3 scripts/session_handover_check.py --strict",
|
||||
"Comment Newline Escaping",
|
||||
"scripts/tea_comment.sh",
|
||||
]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
errors: list[str] = []
|
||||
module.must_contain(
|
||||
commands_doc,
|
||||
[
|
||||
"Session Handover Preflight (Mandatory)",
|
||||
"session_handover_check.py --strict",
|
||||
"Comment Newline Escaping",
|
||||
"scripts/tea_comment.sh",
|
||||
],
|
||||
errors,
|
||||
)
|
||||
assert errors == []
|
||||
|
||||
|
||||
def test_must_contain_fails_when_commands_missing_newline_section_token(tmp_path) -> None:
|
||||
module = _load_module()
|
||||
commands_doc = tmp_path / "commands.md"
|
||||
commands_doc.write_text(
|
||||
"\n".join(
|
||||
[
|
||||
"Session Handover Preflight (Mandatory)",
|
||||
"python3 scripts/session_handover_check.py --strict",
|
||||
"scripts/tea_comment.sh",
|
||||
]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
errors: list[str] = []
|
||||
module.must_contain(
|
||||
commands_doc,
|
||||
["Comment Newline Escaping"],
|
||||
errors,
|
||||
)
|
||||
assert any("Comment Newline Escaping" in err for err in errors)
|
||||
|
||||
Reference in New Issue
Block a user