Conversation
feat: implement PR Sync automation
📝 WalkthroughWalkthroughThis change adds PR Sync automation. It synchronizes implementation pull requests with linked tasks, metadata, parent relationships, and optional Project v2 status. It adds workflow installation, configuration, tests, security policies, and English and Portuguese documentation. ChangesPR Sync
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
project_setup/installer.py (1)
21-21: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert the installer output, not only the source text.
The downstream contract test currently checks that the path string exists in
project_setup/installer.py. It can pass even whentemplate_files()does not return the workflow, or when automatic package discovery omitsproject_setup/pr_sync.py. Assert both paths in the returned(source, target)manifest so this registration is tested end to end.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@project_setup/installer.py` at line 21, Update the contract test around template_files() to assert that both the workflow source path and project_setup/pr_sync.py source path appear in the returned (source, target) manifest, rather than checking only installer.py text; preserve the expected target paths while validating the complete registration end to end.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@project_setup/pr_sync.py`:
- Around line 575-585: Add an --owner-type argument to the project-sync CLI
parser, then propagate its value from the entrypoint through
apply_pr_sync_from_path and sync_project_status to find_project. Ensure the flow
also preserves the existing resolve_owner_type behavior, including
PROJECT_SETUP_OWNER_TYPE when no command-line value is provided.
- Around line 144-145: Update the workflow_run handling around
context_from_pull_request to derive the action from the live_pr state instead of
always passing "synchronize", preserving terminal project statuses for closed
merged and unmerged pull requests. Add coverage for delayed validation runs
covering both closed-merged and closed-unmerged PRs.
- Around line 365-371: Update the exception handling around add_sub_issue and
sync_parent_relationship so “may only have one parent” is not treated as success
automatically. Query the task’s current sub-issue parent, compare it with
parent_number, and only return the existing-link message when they match;
otherwise propagate the failure or perform the required parent replacement.
---
Nitpick comments:
In `@project_setup/installer.py`:
- Line 21: Update the contract test around template_files() to assert that both
the workflow source path and project_setup/pr_sync.py source path appear in the
returned (source, target) manifest, rather than checking only installer.py text;
preserve the expected target paths while validating the complete registration
end to end.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 4df118cd-9f62-45a5-9eaa-73d22652072a
📒 Files selected for processing (14)
.github/workflows/pr-sync.ymlREADME.mdREADME.pt-BR.mddocs/DOCUMENTATION-GUIDE.mddocs/repo/branching-policy.mddocs/repo/branching-policy.pt-BR.mddocs/repo/pr-sync.mddocs/repo/pr-sync.pt-BR.mddocs/repo/project-setup-shared-tool.mdproject_setup.jsonproject_setup/installer.pyproject_setup/pr_sync.pyscripts/validation/repo_quality.pytests/test_pr_sync.py
| live_pr = client.request_json("GET", f"{API_BASE}/repos/{repo}/pulls/{number}") | ||
| return context_from_pull_request(live_pr, "synchronize") |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate and inspect the relevant file and nearby code.
wc -l project_setup/pr_sync.py
ast-grep outline project_setup/pr_sync.py --view expanded | sed -n '1,220p'
echo '--- relevant lines 120-170 ---'
sed -n '120,170p' project_setup/pr_sync.py | nl -ba -v120
echo '--- related functions/usages ---'
rg -n "workflow_run|context_from_pull_request|context_from_event|project_status_for_context|resolve_owner_type|PROJECT_SETUP_OWNER_TYPE|state|state_reason|closed" project_setup/pr_sync.pyRepository: v-Kaefer/Github-Project-Automation
Length of output: 2431
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- project_setup/pr_sync.py lines 100-185 ---'
sed -n '100,185p' project_setup/pr_sync.py
echo '--- project_setup/pr_sync.py lines 370-450 ---'
sed -n '370,450p' project_setup/pr_sync.py
echo '--- test files mentioning workflow_run / pr_sync ---'
git ls-files | rg '(^tests?/|test_)|project_setup' | sed -n '1,200p'
rg -n "workflow_run|push_to_project|project-status|Done|In progress|project_status_for_context|context_from_pull_request" .Repository: v-Kaefer/Github-Project-Automation
Length of output: 12150
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- tests/test_pr_sync.py relevant lines 50-90 and 240-320 ---'
sed -n '50,90p' tests/test_pr_sync.py
sed -n '240,320p' tests/test_pr_sync.py
echo '--- focused read-only verifier for event/status semantics ---'
python3 - <<'PY'
from dataclasses import dataclass
from typing import Any
`@dataclass`
class PullRequestContext:
number: int
action: str
body: str
base_ref: str
head_ref: str
head_repo: str
author: str
draft: bool
merged: bool
def context_from_pull_request(pr: dict[str, Any], action: str = "") -> PullRequestContext:
head = pr.get("head") or {}
base = pr.get("base") or {}
head_repo = (head.get("repo") or {}).get("full_name") or ""
return PullRequestContext(
number=int(pr["number"]),
action=action,
body=pr.get("body") or "",
base_ref=base.get("ref") or "",
head_ref=head.get("ref") or "",
head_repo=head_repo,
author=(pr.get("user") or {}).get("login") or "",
draft=bool(pr.get("draft")),
merged=bool(pr.get("merged")),
)
def project_status_for_context(ctx: PullRequestContext, config: dict[str, Any]) -> str:
mapping = config["projectStatus"]
if ctx.action == "closed":
return str(mapping["merged"] if ctx.merged else mapping["closed"])
if ctx.action == "converted_to_draft" or ctx.draft:
return str(mapping["draft"])
return str(mapping["review"])
DEFAULT_SYNC_CONFIG = {
"projectStatus": {
"review": "Ready",
"draft": "In progress",
"closed": "In progress",
"merged": "Done",
},
}
for state, merged in [("open", False), ("closed", False), ("closed", True), ("draft", False)]:
pr = {"state": state, "merged": merged, "number": 123}
ctx = context_from_pull_request(pr, "synchronize")
print({"state": state, "merged": merged, "action": ctx.action, "project_status": project_status_for_context(ctx, DEFAULT_SYNC_CONFIG)})
PYRepository: v-Kaefer/Github-Project-Automation
Length of output: 4638
Preserve the terminal Project status for late workflow_run events.
workflow_run currently passes "synchronize" to context_from_pull_request, so project_status_for_context still applies the review mapping for closed PRs. Use the live pull request state for the action, and add coverage for closed merged/unmerged PRs after a delayed validation run.
Proposed fix
number = int(related_prs[0]["number"])
live_pr = client.request_json("GET", f"{API_BASE}/repos/{repo}/pulls/{number}")
- return context_from_pull_request(live_pr, "synchronize")
+ action = "closed" if live_pr.get("state") == "closed" else "synchronize"
+ return context_from_pull_request(live_pr, action)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| live_pr = client.request_json("GET", f"{API_BASE}/repos/{repo}/pulls/{number}") | |
| return context_from_pull_request(live_pr, "synchronize") | |
| live_pr = client.request_json("GET", f"{API_BASE}/repos/{repo}/pulls/{number}") | |
| action = "closed" if live_pr.get("state") == "closed" else "synchronize" | |
| return context_from_pull_request(live_pr, action) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@project_setup/pr_sync.py` around lines 144 - 145, Update the workflow_run
handling around context_from_pull_request to derive the action from the live_pr
state instead of always passing "synchronize", preserving terminal project
statuses for closed merged and unmerged pull requests. Add coverage for delayed
validation runs covering both closed-merged and closed-unmerged PRs.
| except Exception as exc: | ||
| text = str(exc).casefold() | ||
| if any( | ||
| marker in text | ||
| for marker in ("already", "exists", "duplicate sub-issues", "may only have one parent") | ||
| ): | ||
| return f"already linked under a parent (requested #{parent_number})." |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
GitHub GraphQL addSubIssue behavior and error response when a sub-issue already has a different parent issue.
💡 Result:
When using the GitHub GraphQL addSubIssue mutation, attempting to add a sub-issue that already has a different parent will result in a 422 Unprocessable Entity error with a message similar to "Sub issue may only have one parent" [1][2]. To handle this scenario, the AddSubIssueInput object provides a replaceParent boolean field [3][4]. Setting replaceParent: true in your mutation input allows the operation to silently move the sub-issue from its existing parent to the new target parent [2][5]. If this field is omitted or set to false, the mutation will fail if the sub-issue is already associated with another parent [2]. Example mutation structure: mutation { addSubIssue(input: { issueId: "PARENT_NODE_ID" subIssueId: "CHILD_NODE_ID" replaceParent: true }) { issue { id } subIssue { id } } } [5]
Citations:
- 1: Overwrite existing parent on
gh issue edit --add-sub-issuecli/cli#13413 - 2: cli/cli@dbe8e34
- 3: https://docs.github.com/en/enterprise-cloud@latest/graphql/reference/issues
- 4: https://docs.github.com/en/enterprise-server@3.18/graphql/reference/issues
- 5: https://github.com/github/awesome-copilot/blob/main/skills/github-issues/references/sub-issues.md
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate file =="
git ls-files | rg '(^|/)project_setup/pr_sync\.py$|(^|/)pr_sync\.py$' || true
echo "== relevant section =="
sed -n '320,390p' project_setup/pr_sync.py
echo "== mutation/addSubIssue references =="
rg -n "addSubIssue|Sub issue may only have one parent|may only have one parent|duplicate sub-issues|already linked|replaceParent|parent" project_setup/pr_sync.pyRepository: v-Kaefer/Github-Project-Automation
Length of output: 4232
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== outline around add_sub_issue =="
ast-grep outline project_setup/pr_sync.py --match add_sub_issue --view expanded || true
echo "== add_sub_issue and nearby helper implementation =="
rg -n -A 80 -B 20 "def add_sub_issue|--add-sub-issue|subIssues|parentIds|parents|projectId" project_setup/pr_sync.py
echo "== mutation usage =="
rg -n "mutation |query |addSubIssue|subIssue|SubIssue|GitHubClient|run_github|gh " project_setup/pr_sync.py | sed -n '1,220p'Repository: v-Kaefer/Github-Project-Automation
Length of output: 299
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== all lines containing add_sub_issue =="
rg -n "add_sub_issue|subissue|sub-issue|subIssue|parentIds|projectId|run_github|gh " project_setup/pr_sync.py || true
echo "== full function references context =="
rg -n -C 6 "add_sub_issue|parent_issue_number|sync_parent_relationship" project_setup/pr_sync.py || true
echo "== line count =="
wc -l project_setup/pr_sync.py
echo "== file top =="
sed -n '1,120p' project_setup/pr_sync.pyRepository: v-Kaefer/Github-Project-Automation
Length of output: 7294
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate issues.py =="
git ls-files | rg '(^|/)issues\.py$' || true
echo "== issues.py outline =="
ast-grep outline project_setup/issues.py --view expanded || true
echo "== add_sub_issue implementation and sub-issue related code =="
rg -n -A 60 -B 20 "def add_sub_issue|add_sub_issue|subIssues|subIssue|parentIds|parents|addSubIssue|migrateSubIssues" project_setup/issues.py || trueRepository: v-Kaefer/Github-Project-Automation
Length of output: 5138
Do not classify a different parent as an existing sub-issue relationship.
add_sub_issue calls GitHub addSubIssue without replaceParent, so may only have one parent can mean the task already belongs to another issue. sync_parent_relationship then returns “already linked under …” even though it did not create or move the required relationship. Query the task’s current sub-issue parent and compare it; fail/swap only if it differs.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@project_setup/pr_sync.py` around lines 365 - 371, Update the exception
handling around add_sub_issue and sync_parent_relationship so “may only have one
parent” is not treated as success automatically. Query the task’s current
sub-issue parent, compare it with parent_number, and only return the
existing-link message when they match; otherwise propagate the failure or
perform the required parent replacement.
| client = require_client() | ||
| project_pat = get_project_pat() | ||
| project_client = GitHubClient(project_pat) if project_pat else None | ||
| return apply_pr_sync_from_path( | ||
| client, | ||
| args.repo, | ||
| args.event_path, | ||
| args.config, | ||
| project_client=project_client, | ||
| project_number=project_number_from_value(args.project_number), | ||
| owner=args.owner, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ast-grep outline project_setup/project.py --items all
rg -n -C 5 'def resolve_owner_type|PROJECT_SETUP_OWNER_TYPE|def find_project' project_setup
rg -n -C 5 'owner_type|find_project\(' project_setup/pr_sync.pyRepository: v-Kaefer/Github-Project-Automation
Length of output: 6553
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ast-grep outline project_setup/pr_sync.py --items all
sed -n '540,610p' project_setup/pr_sync.py
sed -n '470,525p' project_setup/pr_sync.py
sed -n '380,425p' project_setup/project.py
echo '--- main argument handling in pr_sync.py ---'
rg -n -C 4 'def main\(|argparse|add_argument|sync_project_status\(' project_setup/pr_sync.py
echo '--- sync_project_status definitions/usages ---'
rg -n -C 7 'def sync_project_status|sync_project_status\(' project_setup/pr_sync.pyRepository: v-Kaefer/Github-Project-Automation
Length of output: 9259
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '388,448p' project_setup/pr_sync.py
sed -n '400,412p' project_setup/project.pyRepository: v-Kaefer/Github-Project-Automation
Length of output: 2357
Provide a command-line owner_type path for this entrypoint.
resolve_owner_type does read PROJECT_SETUP_OWNER_TYPE, but project_setup/pr_sync.py parses only --owner, and apply_pr_sync_from_path(...) passes no owner_type into sync_project_status(...), so that environment variable is ignored for project-sync. Add --owner-type to the parser and pass it through sync_project_status(...) to find_project(...).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@project_setup/pr_sync.py` around lines 575 - 585, Add an --owner-type
argument to the project-sync CLI parser, then propagate its value from the
entrypoint through apply_pr_sync_from_path and sync_project_status to
find_project. Ensure the flow also preserves the existing resolve_owner_type
behavior, including PROJECT_SETUP_OWNER_TYPE when no command-line value is
provided.
Linked Issue
Milestone
Summary
How to test
Evidence
Known risks
DoD checklist
Summary by CodeRabbit
New Features
Documentation
Tests