Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 37 additions & 11 deletions devloop
Original file line number Diff line number Diff line change
Expand Up @@ -2264,7 +2264,7 @@ run_devloop() {
if [ "$create_pr" = true ] && [ -n "$PASS_COMMIT" ]; then
local pr_id="pull-request-$pass"
event_step "$pr_id" "pushing branch and opening draft PR"
if create_pull_request "$repo" "$FINAL_BRANCH" "$base" "$spec" "$criteria_file" "$FINAL_COMMIT"; then
if create_pull_request "$repo" "$FINAL_BRANCH" "$base" "$spec" "$criteria_file" "$FINAL_COMMIT" "$SOURCE_REPO"; then
event_done "$pr_id" true "draft PR ready: $PULL_REQUEST"
else
STATUS="pr-error"
Expand Down Expand Up @@ -3648,7 +3648,8 @@ create_pull_request() {
local spec="${4:-}"
local criteria_file="${5:-}"
local commit="${6:-}"
ensure_pull_request "$repo" "$branch" "$base" "$spec" "$criteria_file" "$commit"
local source_repo="${7:-}"
ensure_pull_request "$repo" "$branch" "$base" "$spec" "$criteria_file" "$commit" "$source_repo"
}

push_pull_request_branch() {
Expand Down Expand Up @@ -3710,19 +3711,19 @@ draft_pull_request_body() {
local spec="$1"
local criteria_file="$2"
local commit="$3"
local title problem outcome
local source_repo="${4:-}"
local title problem outcome spec_backlink
title="$(spec_title "$spec" 2>/dev/null || true)"
problem="$(spec_section "$spec" "Problem" 2>/dev/null || true)"
outcome="$(spec_section "$spec" "Outcome" 2>/dev/null || true)"
if [ -z "$title" ]; then title="Devloop change"; fi
if [ -z "$problem" ]; then problem="_No problem statement in the source spec._"; fi
if [ -z "$outcome" ]; then outcome="_No outcome described in the source spec._"; fi
if [ -z "$commit" ]; then commit="none"; fi
cat <<EOF
spec_backlink="$(pr_spec_backlink "$source_repo" "$spec" "$commit" 2>/dev/null || true)"
{
cat <<EOF
# $title

Generated by \`devloop --create-pr\`.

## Problem

$problem
Expand All @@ -3740,9 +3741,32 @@ $(if [ -n "$criteria_file" ] && [ -f "$criteria_file" ]; then criteria_block "$c
Review rounds and the final report are posted as PR comments below.

---

Latest commit: $commit
EOF
printf '\n'
if [ -n "$spec_backlink" ]; then printf '%s\n\n' "$spec_backlink"; fi
printf '%s\n' "Generated by [devloop.sh](https://devloop.sh)"
} | sed 's/devloop --create-pr/devloop/g'
}

pr_spec_backlink() {
local source_repo="$1"
local spec="$2"
local commit="$3"
local repo_abs spec_abs rel name_with_owner
[ -n "$source_repo" ] && [ -n "$spec" ] && [ -n "$commit" ] || return 0
[ -d "$source_repo" ] || return 0
repo_abs="$(cd "$source_repo" >/dev/null 2>&1 && pwd -P)" || return 0
spec_abs="$(absolute_existing_file "$spec" 2>/dev/null)" || return 0
case "$spec_abs" in
"$repo_abs"/*) rel="${spec_abs#"$repo_abs"/}" ;;
*) return 0 ;;
esac
[ -n "$rel" ] || return 0
git -C "$repo_abs" cat-file -e "$commit:$rel" 2>/dev/null || return 0
name_with_owner="$(cd "$repo_abs" >/dev/null 2>&1 && gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null)" || return 0
name_with_owner="$(printf '%s\n' "$name_with_owner" | sed '/^[[:space:]]*$/d' | head -n 1)"
[ -n "$name_with_owner" ] || return 0
printf 'Spec: [%s](https://github.com/%s/blob/%s/%s)\n' "$rel" "$name_with_owner" "$commit" "$rel"
}

create_draft_pull_request() {
Expand All @@ -3752,12 +3776,13 @@ create_draft_pull_request() {
local spec="${4:-}"
local criteria_file="${5:-}"
local commit="${6:-}"
local source_repo="${7:-}"
local body_file out
if ! body_file="$(mktemp "${TMPDIR:-/tmp}/devloop-pr-create.XXXXXX")"; then
PULL_REQUEST_ERROR="PR body file failed: mktemp failed"
return 1
fi
if ! draft_pull_request_body "$spec" "$criteria_file" "$commit" > "$body_file"; then
if ! draft_pull_request_body "$spec" "$criteria_file" "$commit" "$source_repo" > "$body_file"; then
PULL_REQUEST_ERROR="PR body file failed"
rm -f "$body_file"
return 1
Expand All @@ -3781,11 +3806,12 @@ ensure_pull_request() {
local spec="${4:-}"
local criteria_file="${5:-}"
local commit="${6:-}"
local source_repo="${7:-}"
PULL_REQUEST_ERROR=""
if ! push_pull_request_branch "$repo" "$branch"; then return 1; fi
if ! lookup_pull_request "$repo" "$branch"; then return 1; fi
if [ -n "$PULL_REQUEST" ]; then return 0; fi
create_draft_pull_request "$repo" "$branch" "$base" "$spec" "$criteria_file" "$commit"
create_draft_pull_request "$repo" "$branch" "$base" "$spec" "$criteria_file" "$commit" "$source_repo"
}

round_review_comment_body() {
Expand Down
50 changes: 48 additions & 2 deletions scripts/devloop_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1266,6 +1266,46 @@ esac
GH
chmod +x "$fake_bin/gh"
chmod +x "$fake_bin/codex" "$fake_bin/claude"

backlink_repo="$work/spec-backlink-repo"
git init -q "$backlink_repo"
git -C "$backlink_repo" config user.email devloop-test@example.com
git -C "$backlink_repo" config user.name "devloop test"
mkdir -p "$backlink_repo/specs"
printf '%s\n' "# Committed Spec" > "$backlink_repo/specs/committed.md"
git -C "$backlink_repo" add specs/committed.md
git -C "$backlink_repo" commit -q -m "add committed spec"
backlink_commit="$(git -C "$backlink_repo" rev-parse HEAD)"
printf '%s\n' "# Uncommitted Spec" > "$backlink_repo/specs/uncommitted.md"
outside_spec="$work/outside-spec.md"
printf '%s\n' "# Outside Spec" > "$outside_spec"
command_spec="$backlink_repo/specs/command.md"
cat > "$command_spec" <<'MARKDOWN'
# Command Spec

## Problem

Generated by `devloop --create-pr`.

## Outcome

The rendered PR body omits the raw command.
MARKDOWN
old_path="$PATH"
PATH="$fake_bin:$PATH"
expected_backlink="Spec: [specs/committed.md](https://github.com/satyaborg/devloop/blob/$backlink_commit/specs/committed.md)"
equals "$(pr_spec_backlink "$backlink_repo" "$backlink_repo/specs/committed.md" "$backlink_commit")" "$expected_backlink" "spec backlink committed"
equals "$(pr_spec_backlink "$backlink_repo" "$backlink_repo/specs/uncommitted.md" "$backlink_commit")" "" "spec backlink uncommitted"
equals "$(pr_spec_backlink "$backlink_repo" "$outside_spec" "$backlink_commit")" "" "spec backlink outside repo"
backlink_body="$(draft_pull_request_body "$backlink_repo/specs/committed.md" "" "$backlink_commit" "$backlink_repo")"
contains "$backlink_body" "$expected_backlink" "spec backlink body"
backlink_body_spec_line="$(printf '%s\n' "$backlink_body" | grep -nF "$expected_backlink" | cut -d: -f1 | head -n 1)"
backlink_body_footer_line="$(printf '%s\n' "$backlink_body" | grep -nFx "Generated by [devloop.sh](https://devloop.sh)" | cut -d: -f1 | tail -n 1)"
[[ -n "$backlink_body_spec_line" && -n "$backlink_body_footer_line" && "$backlink_body_spec_line" -lt "$backlink_body_footer_line" ]] || fail "spec backlink body footer ordering"
not_contains "$(draft_pull_request_body "$command_spec" "" "$backlink_commit" "$backlink_repo")" "devloop --create-pr" "spec backlink body raw command"
PATH="$old_path"
ok "spec backlink"

doctor_output="$(HOME="$install_home" PATH="$bin_dir:$tool_bin:$fake_bin:$PATH" "$bin_dir/devloop" doctor 2>&1)"
contains "$doctor_output" "devloop doctor: ready" "doctor"
contains "$doctor_output" "Required dependencies" "doctor"
Expand Down Expand Up @@ -1782,9 +1822,15 @@ contains "$pr_body" "## Problem" "created PR body"
contains "$pr_body" "The result file is never written during the loop." "created PR body"
contains "$pr_body" "## Outcome" "created PR body"
contains "$pr_body" "The loop writes the result file on accept." "created PR body"
contains "$pr_body" "Latest commit" "created PR body"
if ! printf '%s\n' "$pr_body" | grep -Eq 'Latest commit:[[:space:]]*[0-9a-f]{7,}'; then fail "created PR body missing commit hash"; fi
contains "$pr_body" "Write the result file." "created PR body"
contains "$pr_body" "Generated by [devloop.sh](https://devloop.sh)" "created PR body"
not_contains "$pr_body" "devloop --create-pr" "created PR body"
not_contains "$pr_body" "Latest commit:" "created PR body"
not_contains "$pr_body" "Spec:" "created PR body"
not_contains "$pr_body" "blob/" "created PR body"
pr_body_footer="$(printf '%s\n' "$pr_body" | awk 'NF { line = $0 } END { print line }')"
equals "$pr_body_footer" "Generated by [devloop.sh](https://devloop.sh)" "created PR body footer"
if printf '%s\n' "$pr_body" | grep -Eq '^[0-9a-f]{7,40}$'; then fail "created PR body leaked bare commit hash"; fi
if printf '%s\n' "$pr_body" | grep -q '/Users/'; then fail "created PR body leaked absolute local path"; fi
equals "$(find "$pr_state/comments" -name 'round-*.md' | wc -l | tr -d ' ')" "1" "one round PR comment"
equals "$(find "$pr_state/comments" -name 'final-*.md' | wc -l | tr -d ' ')" "1" "one final PR comment"
Expand Down
Loading