-
Notifications
You must be signed in to change notification settings - Fork 5
feat(code-agent): use PR template for pr_body in structured output #56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -129,12 +129,35 @@ build_pr_body() { | |
| local issue_number="$2" | ||
| local branch="$3" | ||
| local scan_range="$4" | ||
| local pr_body_from_result="${5:-}" # optional: agent-provided pr_body | ||
|
|
||
| local description="" | ||
| if [ -n "${pr_body_from_result}" ]; then | ||
|
rh-hemartin marked this conversation as resolved.
|
||
| # Agent provided pr_body — strip trailing footer lines only. | ||
| description="$(printf '%s\n' "${pr_body_from_result}" | awk ' | ||
| { lines[NR] = $0 } | ||
| END { | ||
| end = NR | ||
| while (end > 0) { | ||
| l = lines[end] | ||
| if (l == "" || l ~ /^Signed-off-by:/ || l ~ /^[Cc]lose[sd]? (#|[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+#)/ || l ~ /^[Ff]ix(e[sd])? (#|[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+#)/ || l ~ /^[Rr]esolve[sd]? (#|[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+#)/) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [MEDIUM] Footer-stripping awk logic is hand-duplicated between production and test with no shared source This Suggestion: Extract the shared awk script into a small sourceable helper file/function that both |
||
| end-- | ||
| else | ||
| break | ||
| } | ||
| for (i = 1; i <= end; i++) | ||
| print lines[i] | ||
| } | ||
| ')" | ||
| fi | ||
|
|
||
| local description | ||
| if [ -z "${commit_body}" ]; then | ||
| description="Automated implementation for issue #${issue_number}." | ||
| else | ||
| description="${commit_body}" | ||
| # Fall back if pr_body was absent or stripped to empty | ||
| if [ -z "${description}" ]; then | ||
| if [ -z "${commit_body}" ]; then | ||
| description="Automated implementation for issue #${issue_number}." | ||
| else | ||
| description="${commit_body}" | ||
| fi | ||
| fi | ||
|
|
||
| echo "${description} | ||
|
|
@@ -250,6 +273,126 @@ count_closes_test "single-closes-with-body" \ | |
| count_closes_test "single-closes-empty-body" \ | ||
| "" "99" | ||
|
|
||
| # Verify pr_body path strips Closes lines (agent may include them) | ||
| count_closes_pr_body_test() { | ||
| local test_name="$1" | ||
| local pr_body="$2" | ||
| local issue_number="$3" | ||
|
|
||
| local actual | ||
| actual="$(build_pr_body "" "${issue_number}" "agent/${issue_number}-fix" "abc123..def456" "${pr_body}")" | ||
|
|
||
| local count | ||
| count=$(echo "${actual}" | grep -c "Closes #${issue_number}" || true) | ||
|
|
||
| if [ "${count}" -ne 1 ]; then | ||
| echo "FAIL: ${test_name}" | ||
| echo " expected exactly 1 'Closes #${issue_number}', found ${count}" | ||
| echo " in body:" | ||
| echo "${actual}" | sed 's/^/ /' | ||
| FAILURES=$((FAILURES + 1)) | ||
| return | ||
| fi | ||
|
|
||
| echo "PASS: ${test_name}" | ||
| } | ||
|
|
||
| count_closes_pr_body_test "single-closes-pr-body-with-closes" \ | ||
|
rh-hemartin marked this conversation as resolved.
|
||
| "## Summary | ||
|
|
||
| Implemented widget rendering. | ||
|
|
||
| Closes #42" "42" | ||
|
|
||
| count_closes_pr_body_test "single-closes-pr-body-with-cross-repo-closes" \ | ||
| "## Summary | ||
|
|
||
| Implemented widget rendering. | ||
|
|
||
| Closes fullsend-ai/agents#42" "42" | ||
|
|
||
| # --- pr_body path test cases --- | ||
|
|
||
| # Helper for pr_body tests (fifth arg is pr_body from result file) | ||
| run_pr_body_test() { | ||
| local test_name="$1" | ||
| local pr_body="$2" | ||
| local issue_number="$3" | ||
| local branch="$4" | ||
| local check_pattern="$5" | ||
| local expect_present="$6" # "yes" or "no" | ||
|
|
||
| local actual | ||
| actual="$(build_pr_body "" "${issue_number}" "${branch}" "abc123..def456" "${pr_body}")" | ||
|
|
||
| if [ "${expect_present}" = "yes" ]; then | ||
| if ! echo "${actual}" | grep -qF "${check_pattern}"; then | ||
| echo "FAIL: ${test_name}" | ||
| echo " expected to find: '${check_pattern}'" | ||
| echo " in body:" | ||
| echo "${actual}" | sed 's/^/ /' | ||
| FAILURES=$((FAILURES + 1)) | ||
| return | ||
| fi | ||
| else | ||
| if echo "${actual}" | grep -qF "${check_pattern}"; then | ||
| echo "FAIL: ${test_name}" | ||
| echo " expected NOT to find: '${check_pattern}'" | ||
| echo " in body:" | ||
| echo "${actual}" | sed 's/^/ /' | ||
| FAILURES=$((FAILURES + 1)) | ||
| return | ||
| fi | ||
| fi | ||
|
|
||
| echo "PASS: ${test_name}" | ||
| } | ||
|
|
||
| # pr_body provided by agent should appear in final PR body | ||
| run_pr_body_test "pr-body-from-result" \ | ||
| $'## Summary\n\nAdded widget rendering.\n\n## Testing\n\nManual test.' \ | ||
| "42" "agent/42-widget" \ | ||
| "Added widget rendering." "yes" | ||
|
|
||
| # pr_body should NOT be word-wrapped (it's verbatim) | ||
| run_pr_body_test "pr-body-verbatim" \ | ||
| $'## Summary\n\nThis is a very long line that would normally be word-wrapped by the legacy commit-body awk logic but should remain intact when coming from pr_body.' \ | ||
| "42" "agent/42-widget" \ | ||
| "This is a very long line that would normally be word-wrapped by the legacy commit-body awk logic but should remain intact when coming from pr_body." "yes" | ||
|
|
||
| # pr_body that strips to empty should fall back to automated description | ||
| run_pr_body_test "pr-body-strips-to-empty" \ | ||
| $'Closes #42' \ | ||
| "42" "agent/42-widget" \ | ||
| "Automated implementation for issue #42." "yes" | ||
|
|
||
| # Cross-repo Closes in trailing footer should be stripped | ||
| run_pr_body_test "pr-body-cross-repo-closes" \ | ||
| $'## Summary\n\nImplemented widget rendering.\n\nCloses fullsend-ai/agents#42' \ | ||
| "42" "agent/42-widget" \ | ||
| "Closes fullsend-ai/agents#42" "no" | ||
|
|
||
| # Closes-like line in body content (not footer) should be preserved | ||
| run_pr_body_test "pr-body-closes-in-content-preserved" \ | ||
| $'## Summary\n\nThis fixes the issue where Closes #99 was not handled.\n\n## Testing\n\nManual test.' \ | ||
| "42" "agent/42-widget" \ | ||
| "Closes #99 was not handled" "yes" | ||
|
|
||
| # Multiple trailing blank lines before footer should all be stripped | ||
| count_closes_pr_body_test "pr-body-trailing-blanks-before-footer" \ | ||
| $'## Summary\n\nDid the thing.\n\n\n\nCloses #42' "42" | ||
|
|
||
| # GitHub auto-close keyword variants should be stripped from footer | ||
| run_pr_body_test "pr-body-fixes-keyword-stripped" \ | ||
| $'## Summary\n\nFixed the bug.\n\nFixes #42' \ | ||
| "42" "agent/42-widget" \ | ||
| "Fixes #42" "no" | ||
|
|
||
| run_pr_body_test "pr-body-resolves-keyword-stripped" \ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [MEDIUM] Missing test coverage for common closing-keyword variants the stripping regex doesn't match The stripping regex requires a literal space between the keyword and Suggestion: Add test cases for |
||
| $'## Summary\n\nResolved the issue.\n\nResolves #42' \ | ||
| "42" "agent/42-widget" \ | ||
| "Resolves #42" "no" | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Test helper — reimplements the no-op detection logic from post-code.sh | ||
| # so we can test it without a git repo or network access. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -446,15 +446,46 @@ fi | |
| echo "Creating PR..." | ||
|
|
||
| COMMIT_SUBJECT="$(git log -1 --format='%s' HEAD)" | ||
| COMMIT_BODY_RAW="$(git log -1 --format='%b' HEAD | sed '/^Signed-off-by:/d' | sed '/^Closes #/d' | sed -e :a -e '/^\n*$/{ $d; N; ba; }')" | ||
|
|
||
| COMMIT_BODY="$(echo "${COMMIT_BODY_RAW}" | awk ' | ||
| /^$/ { if (buf) print buf; print; buf=""; next } | ||
| /^[-*#>]|^ / { if (buf) print buf; buf=""; print; next } | ||
| /^Closes / { if (buf) print buf; buf=""; print; next } | ||
| { buf = (buf ? buf " " $0 : $0) } | ||
| END { if (buf) print buf } | ||
| ')" | ||
|
|
||
| # Read pr_body from agent output. Fall back to commit body if absent. | ||
| PR_BODY_FROM_RESULT="" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [HIGH]
Suggestion: Run |
||
| if [ -n "${RESULT_FILE}" ]; then | ||
| if ! PR_BODY_FROM_RESULT="$(jq -r '.pr_body // empty' "${RESULT_FILE}" 2>/dev/null)"; then | ||
| echo "::notice::pr_body not found in result file; using commit body" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [minor] Heads up — this fires on jq parse errors, not on a missing |
||
| PR_BODY_FROM_RESULT="" | ||
| fi | ||
| fi | ||
|
|
||
| if [ -n "${PR_BODY_FROM_RESULT}" ]; then | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [MEDIUM] When In the legacy (no- Suggestion: When |
||
| # Agent provided pr_body — strip trailing footer lines (Signed-off-by, | ||
| # GitHub auto-close keywords) so the script appends them once. | ||
| # Only strips from the end to preserve matching lines in body content. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [MEDIUM] The legacy commit-body path strips Suggestion: Either strip these patterns globally from |
||
| COMMIT_BODY="$(printf '%s\n' "${PR_BODY_FROM_RESULT}" | awk ' | ||
| { lines[NR] = $0 } | ||
| END { | ||
| end = NR | ||
| while (end > 0) { | ||
| l = lines[end] | ||
| if (l == "" || l ~ /^Signed-off-by:/ || l ~ /^[Cc]lose[sd]? (#|[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+#)/ || l ~ /^[Ff]ix(e[sd])? (#|[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+#)/ || l ~ /^[Rr]esolve[sd]? (#|[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+#)/) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [MEDIUM] Footer-stripping regex is not end-anchored — can silently delete real trailing content, not just the closing reference Each alternative ( Suggestion: Anchor with |
||
| end-- | ||
| else | ||
| break | ||
| } | ||
| for (i = 1; i <= end; i++) | ||
| print lines[i] | ||
| } | ||
| ')" | ||
| else | ||
|
rh-hemartin marked this conversation as resolved.
rh-hemartin marked this conversation as resolved.
|
||
| # Fall back to unwrapped commit body (legacy path) | ||
| COMMIT_BODY_RAW="$(git log -1 --format='%b' HEAD | sed '/^Signed-off-by:/d' | sed '/^Closes #/d' | sed -e :a -e '/^\n*$/{ $d; N; ba; }')" | ||
| COMMIT_BODY="$(echo "${COMMIT_BODY_RAW}" | awk ' | ||
| /^$/ { if (buf) print buf; print; buf=""; next } | ||
| /^[-*#>]|^ / { if (buf) print buf; buf=""; print; next } | ||
| /^Closes / { if (buf) print buf; buf=""; print; next } | ||
| { buf = (buf ? buf " " $0 : $0) } | ||
| END { if (buf) print buf } | ||
| ')" | ||
| fi | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Ensure PR title includes an issue reference. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[HIGH]
maxLengthexactly matches GitHub's hard body-length limit, leaving no headroom for the footerpost-code.shalways appends65536is GitHub's exact PR/issue body character ceiling.post-code.sh(lines 515-526) unconditionally appends a ~150-250 char footer (---,Closes #N, verification checklist) after the agent'spr_body. A schema-validpr_bodynear the ceiling pushes the assembled body over GitHub's limit, sogh pr createfails outright with no automated recovery path.Suggestion: Lower
maxLengthto reserve headroom for the footer (e.g. ~65000 minus a safety margin), or validate/truncate the assembled body length inpost-code.shbefore callinggh pr create.