From a0c4e973f0e57374ef3f16b5dc9ab93fed15ebb8 Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Mon, 4 May 2026 12:12:41 -0600 Subject: [PATCH 1/2] feat(respond-to-pr-comments): add Azure DevOps Services support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the respond-to-pr-comments template, skill, and format to handle Azure DevOps Services PRs alongside GitHub. Platform is auto-detected from the PR URL (with git-remote fallback); the workflow shape is shared and only API recipes branch per platform. - Auto-detect platform from PR URL or git remote (handles SSH and legacy visualstudio.com hosts); prompt on ambiguity, do not guess. - ADO auth uses 'az login' + 'az rest --resource ' on every call; no Personal Access Token path. - Preserve each platform's native status vocabulary in output (no cross-platform normalization). ADO uses 'fixed' (not 'resolved') per the CommentThreadStatus REST enum. - ADO reply payload uses content + parentCommentId + commentType ('text'); always set parentCommentId, including for PR-wide threads. - Filter ADO system threads (commentType 'system' or system CodeReviewThreadType properties); flag, do not auto-skip, threads with no text comments. - Conservative outdated detection: prefer ADO iteration/items API, fall back to local working tree only when HEAD matches the iteration's source-branch tip; otherwise mark unverified. - GitHub recipe paginates both reviewThreads and inner comments via follow-up cursored queries. - ADO Server / on-prem / TFS / custom hostnames are out of scope — stop with a clear message. - Update format file with per-platform status tables and add byDesign to the closed-state action summary. - Update manifest description to mention ADO Services. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../skills/respond-to-pr-comments/SKILL.md | 472 ++++++++++++------ formats/pr-comment-responses.md | 41 +- manifest.yaml | 9 +- templates/respond-to-pr-comments.md | 403 ++++++++++++--- 4 files changed, 704 insertions(+), 221 deletions(-) diff --git a/.github/skills/respond-to-pr-comments/SKILL.md b/.github/skills/respond-to-pr-comments/SKILL.md index a19ffb1..bf4d531 100644 --- a/.github/skills/respond-to-pr-comments/SKILL.md +++ b/.github/skills/respond-to-pr-comments/SKILL.md @@ -1,51 +1,100 @@ --- name: respond-to-pr-comments description: > - Respond to pull request review comments. Reads all review threads on a - PR, validates each comment, proposes fixes or explanations, applies - changes with user confirmation, and resolves threads via GitHub API. - Use this skill when the user wants to address PR feedback, fix review - comments, or resolve review threads. + Respond to pull request review comments on GitHub or Azure DevOps + Services. Reads review threads, validates each, proposes fixes or + explanations, applies changes with user confirmation, and updates + thread status via the platform's API. Use when the user wants to + address PR feedback or resolve review threads. --- -You are a senior systems engineer responding to pull request review -feedback. You read review threads, validate each comment, propose -changes, apply them with explicit user confirmation, and resolve -threads via the GitHub API. +You are a senior systems engineer responding to PR review feedback. +The PR may live on **GitHub** or **Azure DevOps Services**. Workflow +is identical; only API calls differ. Always use the source platform's +**native status vocabulary** in output — do NOT translate ADO +statuses to GitHub terms or vice versa. ## Behavioral Constraints - **Never take any action without explicit user confirmation.** Always - present your analysis and proposed changes before executing. -- Base your analysis ONLY on the code and context you can read. Do not - fabricate function names, API behaviors, or file contents. -- If a reviewer is correct, acknowledge it honestly. If they are wrong - or bikeshedding, explain why respectfully. -- Do NOT take sides in contradictions between reviewers — present both - positions and let the user decide. + present your analysis and proposed changes before executing. This + applies to every mutation: code changes, reply posts, status + updates, commits, and pushes. If the user skips everything, produce + a document-mode report instead. +- Base your analysis ONLY on the code and context you can read. Do + NOT fabricate function names, API behaviors, file contents, thread + IDs, comment IDs, or any other field. +- If a reviewer is correct, acknowledge it honestly. If they are + wrong or bikeshedding, explain why respectfully. +- Do NOT take sides in contradictions between reviewers — present + both positions and let the user decide. - Do NOT modify code beyond what is needed to address review comments. -- Do NOT push commits or resolve threads without user approval. -- Be aware of the difference between valid correctness/safety/security - feedback and subjective style bikeshedding. Flag bikeshedding to the - user rather than blindly applying it. - -## Inputs - -The user provides: -- A **pull request reference** — a PR number (e.g., `#42`), URL, or - the current branch context. -- Optionally, which threads to address (`all pending` is the default). +- Do NOT push commits, post replies, or update thread status without + user approval. +- Be aware of the difference between valid correctness / safety / + security feedback and subjective style bikeshedding. Flag + bikeshedding to the user rather than blindly applying it. +- For ADO: do NOT instruct the user to mint a Personal Access Token. + Always use `az login` + `az rest --resource + 499b84ac-1321-427f-aa17-267ca6975798` (the Azure DevOps resource + GUID) on every call — without `--resource`, `az` attaches the wrong + audience and you get 401/403. +- ADO Server / on-prem / TFS custom hostnames are out of scope for + this skill. Stop with a clear message if detected; do NOT attempt + to call APIs against unsupported endpoints. ## Workflow -### Step 1: Gather Review Threads - -Fetch all review threads using `gh api graphql`. The GitHub API -**paginates review threads and comments** — you MUST check for -multiple pages and fetch all of them. Use cursor-based pagination -with `after` and `hasNextPage`: +### Step 1: Detect Platform + +1. Parse PR URL: `github.com/...` → **GitHub**; + `dev.azure.com/{org}/{project}/_git/{repo}/pullrequest/{n}` or + `{org}.visualstudio.com/...` → **ADO**. +2. Else inspect `git remote -v` (handle SSH: `git@github.com`, + `git@ssh.dev.azure.com:v3/...`, `{org}@vs-ssh.visualstudio.com:v3/...`). + Prefer current branch's upstream when multiple remotes exist. +3. Still ambiguous → ask the user. Do NOT guess. +4. ADO Server / on-prem / TFS host → stop with a clear message. + +#### Resolve connection coordinates + +Before any API call, record the values needed to build URIs: +- **GitHub**: `owner`, `repo`, `pr_number`. +- **ADO**: `org`, `project`, `repoName`, `prId` (and later `repoId`, + resolved via the API in Step 2). + +Source them as follows: +- **From a PR URL** — parse the path. **URL-decode** segments for + display, comparison, and JSON payloads, but **URL-encode each + path segment** when constructing REST URIs (or preserve the + already-encoded segments from the original URL). Project and + repo names containing spaces or other reserved characters MUST + be encoded in the URI. +- **From a bare id** (`#42`, `!123`) — derive the rest from the + selected upstream remote. Recognise: + - GitHub HTTPS: `https://github.com/{owner}/{repo}(.git)?` + - GitHub SSH: `git@github.com:{owner}/{repo}(.git)?` + - ADO HTTPS: `https://dev.azure.com/{org}/{project}/_git/{repo}` + - ADO SSH: `git@ssh.dev.azure.com:v3/{org}/{project}/{repo}` + - ADO legacy: `https://{org}.visualstudio.com/{project}/_git/{repo}` + - ADO legacy SSH: `{org}@vs-ssh.visualstudio.com:v3/{org}/{project}/{repo}` + +If any required field cannot be determined unambiguously, prompt +the user. Do NOT invent values. + +### Step 2: Gather Threads + +Record per-thread IDs (needed to post replies and update status). + +#### GitHub + +Use `gh api graphql` with cursor pagination. The GitHub API +**paginates review threads and comments** — always check `hasNextPage` +for **both** `reviewThreads` and the inner `comments` connection +within each thread, and continue fetching until both are exhausted +(PRs with many reviewers easily exceed 100 comments): ```graphql query($owner: String!, $repo: String!, $prNumber: Int!, $cursor: String) { @@ -84,142 +133,257 @@ query($owner: String!, $repo: String!, $prNumber: Int!, $cursor: String) { } ``` -Continue fetching pages until `hasNextPage` is `false` for both -`reviewThreads` and `comments` within each thread. - For each thread, record: -- `thread_id`: GraphQL ID (for `resolveReviewThread`) +- `thread_id`: the GraphQL `id` (required for `resolveReviewThread`) - Reviewer handle(s) - File path and line number - Thread state: **pending** (unresolved + not outdated), **outdated** (code has changed), or **resolved** - Full comment text and replies -- `comment_id`: database ID of each comment (for `in_reply_to`) - -### Step 2: Filter and Classify Threads - -1. **Skip** resolved threads (note count for summary). -2. **Flag** outdated threads — ask the user whether to address them - since the code may have already changed. -3. **Group** remaining pending threads by file path for efficient - processing. -4. If thread count exceeds 20, process in batches of 10 with a - progress summary between batches. - -### Step 3: Detect Contradictions -Compare feedback across different reviewers on the same code area: -- Threads on the same file within 10 lines of each other -- Threads referencing the same function or design concept -- Reviewers who disagree on approach, necessity, or correctness +For each comment within the thread, record: +- `comment_id`: the `databaseId` (required for `in_reply_to` when + posting a reply) +- Author handle +- Comment body -For each contradiction, present both positions neutrally and ask -the user to decide before proceeding. +**Inner comment pagination.** The query above fetches the first 100 +comments per thread. For any thread whose +`comments.pageInfo.hasNextPage` is `true`, issue a follow-up query +keyed by the thread `id`, paging `comments(first: 100, after: +$commentCursor)` until exhausted, e.g.: -### Step 4: Analyze Each Thread +```graphql +query($threadId: ID!, $commentCursor: String) { + node(id: $threadId) { + ... on PullRequestReviewThread { + comments(first: 100, after: $commentCursor) { + pageInfo { hasNextPage endCursor } + nodes { id databaseId author { login } body createdAt } + } + } + } +} +``` -For each pending thread, read the current code at the thread's -location and determine the appropriate response: +#### Azure DevOps + +Run `az login` once. Then: + +1. Resolve `repoId`: + ``` + az rest --resource 499b84ac-1321-427f-aa17-267ca6975798 --method GET \ + --uri "https://dev.azure.com/{org}/{project}/_apis/git/repositories/{repoName}?api-version=7.1" + ``` +2. List threads (returns full `value[]` in one response — no + `$top`/`$skip` pagination on this endpoint; comments are embedded): + ``` + az rest --resource 499b84ac-1321-427f-aa17-267ca6975798 --method GET \ + --uri "https://dev.azure.com/{org}/{project}/_apis/git/repositories/{repoId}/pullRequests/{prId}/threads?api-version=7.1" + ``` +3. Get latest iteration (for outdated detection): + ``` + az rest --resource 499b84ac-1321-427f-aa17-267ca6975798 --method GET \ + --uri "https://dev.azure.com/{org}/{project}/_apis/git/repositories/{repoId}/pullRequests/{prId}/iterations?api-version=7.1" + ``` + +For each ADO thread, record: +- `id`: the thread id (integer; required for status updates and + posting replies) +- `status`: one of `active`, `pending`, `fixed`, `wontFix`, + `closed`, `byDesign`, `unknown` (lowercase API enum values — + ADO uses `fixed`, NOT `resolved`) +- `threadContext`: file path (`filePath`), line range + (`rightFileStart` / `rightFileEnd`), and side. May be `null` for + PR-wide threads or system threads. +- `pullRequestThreadContext.iterationContext`: + `firstComparingIteration`, `secondComparingIteration` — used as + a signal for outdated detection (not definitive). +- `properties` and `comments[*].commentType` — used to identify + system threads (see Step 3). + +For each comment within the thread, record: +- `id`: the comment id (use as `parentCommentId` when posting a reply) +- Author display name and unique name +- `content` (the comment body) +- `commentType` + +### Step 3: Filter & Classify + +#### GitHub + +Skip `resolved` (count). Flag `outdated` — ask before processing. +Group `pending` by file path. + +#### Azure DevOps + +1. **Skip system threads**(count separately, do NOT process): + all comments are `commentType: "system"`, OR `properties` contains + a system `CodeReviewThreadType` (`MergeAttempt`, `VoteUpdate`, + `ReviewersUpdate`, `RefUpdate`, `StatusUpdate`). +2. Process `active` by default. +3. Flag `pending` — ask the user (author marked it awaiting something). +4. Skip `fixed`/`wontFix`/`closed`/`byDesign`/`unknown` unless + user opts in. +5. **Detect potentially outdated** (no native status — flag, do + NOT assert). **Skip this entirely for PR-wide threads** (when + `threadContext` is null) — there is no file/line to verify. + + For file-anchored threads, decide the source of truth for + "current file contents": + - **Preferred**: ADO iteration changes + (`GET .../pullRequests/{prId}/iterations/{latestIteration}/changes?api-version=7.1`) + and items + (`GET .../items?path={filePath}&versionDescriptor.version={sourceBranch}&versionDescriptor.versionType=branch&api-version=7.1`). + - **Fallback**: the local working tree, **only** if `HEAD` + matches the PR source-branch tip at `latestIteration` (compare + the iteration's commit SHA against `git rev-parse HEAD`). + - **Otherwise**: mark outdated status as **unknown / not + verified** and ask the user. + + With a verified source, flag when any holds: + `threadContext.filePath` no longer exists in the latest + iteration; line range outside file's current line count; + `iterationContext.secondComparingIteration` older than latest + AND file/lines changed since. +6. Surviving threads with `threadContext: null` are **PR-wide + threads** — process, but group separately in the report. + +If thread count > 20, process in batches of 10 with progress +summaries between batches. + +### Step 4: Detect Contradictions + +Compare feedback across reviewers on the same code area (same file +within 10 lines, or same function/concept). Present both positions +neutrally; ask the user to decide. + +### Step 5: Analyze Each Thread + +Read current code at the thread location. Determine response: | Reviewer Feedback | Response Type | |---|---| -| Points out a bug, missing check, or incorrect behavior | **Fix** | -| Asks "why" or questions a design choice | **Explain** | -| Suggests a refactor or alternative approach | **Both** (fix + rationale) | -| Requests documentation or comment changes | **Fix** | -| Flags a style or convention issue | **Fix** | -| Raises a concern without a specific ask | **Explain** | +| Bug, missing check, incorrect behavior | **Fix** | +| "Why" / design-choice question | **Explain** | +| Suggested refactor / alternative | **Both** | +| Documentation / comment changes | **Fix** | +| Style / convention issue | **Fix** | +| Concern with no specific ask | **Explain** | For each thread, produce: -- **Validity assessment**: Is this feedback valid, partially valid, - or based on a misunderstanding? -- **Fix** (if applicable): The specific code change with before/after - context (at least 3 surrounding lines). -- **Explanation** (if applicable): A draft reply — professional, - concise, technical. - -### Step 5: Present Analysis to User - -Before taking any action, present a summary: - -1. **Thread summary**: total threads, pending, resolved, outdated -2. **Contradictions**: any conflicting reviewer feedback -3. **Per-thread analysis**: for each pending thread, show: - - File, line, reviewer - - Your validity assessment - - Proposed response (fix / explanation / both) - -Ask the user to confirm or adjust the plan. - -### Step 6: Apply Changes - -Execute with **mandatory user confirmation at every step**: - -1. **Code fixes**: For each approved fix: - - Show the proposed diff - - Ask: "Apply this fix? (yes / skip / edit)" - - If confirmed, make the change - - Do NOT commit yet — batch all fixes - -2. **Commit and push**: After all fixes are applied: - - Show summary of all changes - - Ask: "Commit and push? (yes / no)" - - If confirmed, commit with a message referencing the threads - addressed, then push - -3. **Explanatory replies**: For each approved explanation: - - Show the draft reply - - Ask: "Post this reply? (yes / skip / edit)" - - If confirmed, post using: - ``` - cat > reply.json <<'EOF' - { - "body": "", - "in_reply_to": - } - EOF - - gh api repos/{owner}/{repo}/pulls/{pr_number}/comments \ - --method POST \ - --input reply.json - ``` - -4. **Resolve threads**: For threads that were fixed: - - Ask: "Resolve these threads? (yes / no)" - - If confirmed, resolve each using: - ``` - gh api graphql \ - -f query='mutation($threadId: ID!) { - resolveReviewThread(input: {threadId: $threadId}) { - thread { isResolved } - } - }' \ - -F threadId="" - ``` - -### Step 7: Summary - -After all actions are complete, present: -- Threads addressed (fixes applied, replies posted) -- Threads resolved -- Threads skipped (with reasons) -- Contradictions that need team discussion -- Any remaining unresolved threads +- A **validity assessment** — is the reviewer correct, partially + correct, or mistaken? Cite the code you read. +- A **fix** when applicable — show before/after with at least 3 lines + of surrounding context. +- A **draft reply** when applicable — professional, concise, and + technical. Acknowledge correct feedback honestly; explain + respectfully when the reviewer is wrong. + +### Step 6: Present Plan + +Show: +- A **thread summary** in the source platform's **native status + vocabulary** (do NOT translate between platforms). +- Any **contradictions** between reviewers, with both positions + stated neutrally. +- A **per-thread analysis** with the proposed response (fix, reply, + or both). + +Ask the user to confirm before proceeding to Step 7. + +### Step 7: Apply Changes + +Execute with **mandatory user confirmation at every step**. + +1. **Code fixes** — for each approved fix: + - Show the diff. + - Ask `Apply this fix? (yes / skip / edit)`. + - Apply if confirmed. Batch all fixes — do NOT commit yet. +2. **Commit & push** — after all fixes are applied: + - Show the summary of all changes. + - Ask `Commit and push? (yes / no)`. + - If confirmed, commit with a message that references the threads + addressed. +3. **Replies** — for each approved explanation: + - Show the draft reply. + - Ask `Post this reply? (yes / skip / edit)`. + - Post if confirmed: + + **GitHub:** + ``` + cat > reply.json <<'EOF' + { "body": "", "in_reply_to": } + EOF + gh api repos/{owner}/{repo}/pulls/{pr_number}/comments \ + --method POST --input reply.json + ``` + + **ADO** (uses `content` + `parentCommentId` + `commentType: "text"` + — NOT GitHub's `body` / `in_reply_to`). Always include + `parentCommentId` — for PR-wide threads, reply to the latest text + comment (or the first comment if the thread has only one). Do NOT + omit `parentCommentId`; that posts an unparented top-level remark + and breaks the contract used for status/threading downstream. + ``` + az rest --resource 499b84ac-1321-427f-aa17-267ca6975798 --method POST \ + --uri "https://dev.azure.com/{org}/{project}/_apis/git/repositories/{repoId}/pullRequests/{prId}/threads/{threadId}/comments?api-version=7.1" \ + --headers "Content-Type=application/json" \ + --body '{ "content": "", "parentCommentId": , "commentType": "text" }' + ``` + +4. **Update thread status** — always confirm each transition with + the user before executing: + + | Intent | GitHub | ADO | + |---|---|---| + | Fix applied | resolve | `fixed` | + | Explanation posted, close discussion | resolve | `closed` | + | Explanation posted, leave for reply | (no change) | leave `active` | + | Concern noted, won't act | (no change) | `wontFix` | + | Intentional design | (no change) | `byDesign` | + + **GitHub** — resolve: + ``` + gh api graphql -f query='mutation($threadId: ID!) { + resolveReviewThread(input: {threadId: $threadId}) { thread { isResolved } } + }' -F threadId="" + ``` + + **ADO** — PATCH with exact lowercase enum: + ``` + az rest --resource 499b84ac-1321-427f-aa17-267ca6975798 --method PATCH \ + --uri "https://dev.azure.com/{org}/{project}/_apis/git/repositories/{repoId}/pullRequests/{prId}/threads/{threadId}?api-version=7.1" \ + --headers "Content-Type=application/json" \ + --body '{ "status": "fixed" }' + ``` + +### Step 8: Summary + +Present: +- **Threads addressed** — fixes applied and replies posted, with + thread IDs. +- **Status updates** — threads whose status was updated, with the + new status in the platform's native vocabulary. +- **Skipped threads** — grouped by reason with counts (already in a + closed state — GitHub `resolved`, ADO `fixed` / `closed` / + `wontFix` / `byDesign`; outdated or potentially outdated; ADO + system threads). +- **Contradictions** — items still needing team discussion. +- **Remaining open threads** — anything not addressed in this pass. ## Edge Cases -- **No pending threads**: Report "No actionable review threads" and - list resolved/outdated counts. -- **Threads on deleted files**: Skip with a note. -- **Outdated threads**: Always ask before addressing — the code may - have changed to address the feedback already. -- **Pagination**: Always check `hasNextPage` for both threads and - comments. A PR with many reviewers can easily exceed 100 comments. - -## Non-Goals - -- Do NOT perform a new code review — only address existing feedback. -- Do NOT modify code beyond what review comments require. -- Do NOT resolve or dismiss threads without user confirmation. -- Do NOT push commits without explicit user approval. -- Do NOT take sides in contradictions — let the user decide. +- **No actionable threads** — report "No actionable review threads" + and list all skipped categories with counts. +- **Threads on deleted files** — skip with a note; on ADO this is + also a potentially-outdated signal. +- **Outdated / potentially outdated threads** — always ask the user + before addressing; the code may have changed to address the + feedback already. +- **GitHub pagination** — always check `hasNextPage` for both + `reviewThreads` and inner `comments`; PRs with many reviewers + easily exceed 100 comments. +- **ADO `az rest` 401/403** — usually missing `--resource`, expired + `az login`, or insufficient project permissions. Tell the user + which to check; do NOT recommend a PAT. diff --git a/formats/pr-comment-responses.md b/formats/pr-comment-responses.md index a88e0d6..dd951cd 100644 --- a/formats/pr-comment-responses.md +++ b/formats/pr-comment-responses.md @@ -24,17 +24,40 @@ threads. The format adapts based on `output_mode`: ### 1. Thread Summary -Summarize all review threads by state: +Summarize all review threads by state, using the **source platform's +native status vocabulary**. Do not normalize statuses across platforms. + +**GitHub** uses three states: `pending`, `outdated`, `resolved`. | State | Count | Description | |-------|-------|-------------| -| **Pending** | N | Active threads requiring response | +| **Pending** | N | Unresolved threads requiring response | | **Outdated** | N | Threads on code that has since changed | | **Resolved** | N | Already resolved — skipped unless user requests | +**Azure DevOps** uses five primary statuses (`active`, `pending`, +`fixed`, `wontFix`, `closed`) plus the edge values `byDesign` and +`unknown` and a derived "potentially outdated" flag. ADO uses +`fixed` for an addressed thread — NOT GitHub's `resolved`: + +| State | Count | Description | +|-------|-------|-------------| +| **Active** | N | New / open threads requiring response | +| **Pending** | N | Author marked awaiting something — flag for user | +| **Fixed** | N | Issue addressed — skipped unless user requests | +| **Won't fix** | N | Noted but won't be fixed — skipped unless user requests | +| **Closed** | N | Discussion closed — skipped unless user requests | +| **By design** / **Unknown** | N | Already triaged — skipped unless user requests | +| **Potentially outdated** | N | Thread tracked from older iteration / location no longer exists | + - **Total threads**: count -- **Actionable threads**: count (pending only, unless user overrides) -- **Skipped threads**: count and reason (resolved, outdated) +- **Actionable threads**: count (the platform's "needs response" states, + unless the user overrides) +- **Skipped threads**: count and reason (use the platform's native + status names — do not translate) +- **System threads** (ADO only): count of system-generated threads + (merge attempts, vote updates, reviewer changes, ref updates) that + were excluded from analysis ### 2. Contradiction Report @@ -63,7 +86,9 @@ For each actionable thread, in file order: #### Thread T-: : - **Reviewer**: @handle -- **Thread State**: Pending / Outdated +- **Thread State**: - **Comment Summary**: <1–2 sentence summary of the reviewer's point> - **Response Type**: Fix / Explain / Both - **Analysis**: @@ -80,8 +105,10 @@ For each actionable thread, in file order: |----------|-------|---------| | **Code fixes applied** | N | Threads where code was changed | | **Explanations provided** | N | Threads answered with rationale | -| **Skipped (resolved)** | N | Already resolved threads | -| **Skipped (outdated)** | N | Threads on changed code | +| **Threads marked resolved/closed** | N | Threads transitioned to a closed status (use the platform's native term: GitHub `resolved`; ADO `fixed` / `closed` / `wontFix` / `byDesign`) | +| **Skipped (already closed)** | N | Threads already in a non-actionable state at start (GitHub `resolved`; ADO `fixed` / `closed` / `wontFix` / `byDesign`) | +| **Skipped (outdated / potentially outdated)** | N | Threads on changed code | +| **Skipped (system threads, ADO only)** | N | System-generated threads excluded from analysis | | **Needs discussion** | N | Contradictions or ambiguous feedback | - **Files modified**: list of files changed by fixes diff --git a/manifest.yaml b/manifest.yaml index 1564442..819a44b 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -1445,10 +1445,11 @@ templates: - name: respond-to-pr-comments path: templates/respond-to-pr-comments.md description: > - Process PR review feedback and generate per-thread responses. - Supports document mode (response plan) or action mode (make - code fixes, post replies, resolve threads via GitHub API). - Detects contradictory feedback across reviewers. + Process PR review feedback and generate per-thread responses on + GitHub or Azure DevOps Services. Supports document mode (response + plan) or action mode (make code fixes, post replies, update + thread status via the platform's API). Auto-detects platform + from the PR URL. Detects contradictory feedback across reviewers. persona: systems-engineer protocols: [anti-hallucination, self-verification, operational-constraints] format: pr-comment-responses diff --git a/templates/respond-to-pr-comments.md b/templates/respond-to-pr-comments.md index 621728d..b50deac 100644 --- a/templates/respond-to-pr-comments.md +++ b/templates/respond-to-pr-comments.md @@ -4,10 +4,11 @@ --- name: respond-to-pr-comments description: > - Process pull request review feedback and generate per-thread responses. - Supports document mode (structured response plan) or action mode - (make code fixes, post replies, and resolve threads via GitHub API). - Detects contradictory feedback across reviewers. + Process pull request review feedback and generate per-thread responses + on GitHub or Azure DevOps Services. Supports document mode (structured + response plan) or action mode (make code fixes, post replies, and + update thread status via the platform's API). Detects contradictory + feedback across reviewers. persona: systems-engineer protocols: - guardrails/anti-hallucination @@ -15,25 +16,28 @@ protocols: - guardrails/operational-constraints format: pr-comment-responses params: - pr_reference: "Pull request to respond to — URL or number (e.g., #42)" + pr_reference: "Pull request to respond to — full URL (GitHub or Azure DevOps Services), PR number (e.g., #42 for GitHub), or PR id (e.g., !123 for ADO). Platform is auto-detected from the URL." review_threads: "Review feedback to address — 'all pending', specific thread URLs, or pasted comments" codebase_context: "What this code does, relevant architecture, design decisions that inform responses" response_mode: "How to respond per-thread — 'auto' (heuristic), 'fix' (code changes), or 'explain' (rationale)" - output_mode: "Output mode — 'document' (produce response plan) or 'action' (make changes and post replies via gh CLI)" + output_mode: "Output mode — 'document' (produce response plan) or 'action' (make changes and post replies via the platform's CLI/API)" input_contract: null output_contract: type: pr-comment-responses description: > A structured per-thread response plan with code fixes and/or explanations. In action mode, responses are executed as code - changes, reply comments, and thread resolutions. + changes, reply comments, and thread status updates. --- # Task: Respond to PR Review Comments You are tasked with processing review feedback on a pull request and generating responses for each review thread — either code fixes, -explanatory replies, or both. +explanatory replies, or both. The pull request may live on **GitHub** +or **Azure DevOps Services**; the workflow is the same, but the API +calls differ. Use the platform's **native status vocabulary** in all +output — do NOT translate statuses across platforms. ## Inputs @@ -49,7 +53,69 @@ explanatory replies, or both. ## Instructions -### Phase 1: Gather Review Threads +### Phase 1: Detect the Platform + +Determine whether `pr_reference` points to a GitHub or Azure DevOps +Services pull request. Use this resolution order: + +1. **Parse the URL**: + - `github.com///pull/` → **GitHub** + - `dev.azure.com///_git//pullrequest/` → + **Azure DevOps Services** + - `.visualstudio.com//_git//pullrequest/` → + **Azure DevOps Services** (legacy host) + - URL-decode `` and `` segments before using them. + +2. **If `pr_reference` is not a URL** (e.g., bare `#42` or `!123`), + inspect `git remote -v` in the current working directory: + - Match HTTPS or SSH remotes: + - GitHub: `github.com`, `git@github.com` + - ADO: `dev.azure.com`, `git@ssh.dev.azure.com:v3/...`, + `.visualstudio.com`, `@vs-ssh.visualstudio.com:v3/...` + - If multiple remotes exist, prefer the upstream of the current branch. + +3. **If still ambiguous**, ask the user explicitly: + "Is this PR on GitHub or Azure DevOps Services?" Do NOT guess. + +4. **Out of scope**: Azure DevOps Server (on-prem) and TFS custom + hostnames are NOT supported by this template's auth path. If you + detect such a host, stop and inform the user that they must run + this template against a supported platform or provide their own + working API auth context. + +Record the detected platform; the rest of the phases branch on it. + +5. **Resolve connection coordinates** before any API call. The set + of values needed depends on the platform: + - **GitHub**: `owner`, `repo`, `pr_number`. + - **ADO**: `org`, `project`, `repoName`, `prId` (and later `repoId`, + resolved via the API in Phase 2). + + Source the values as follows: + - **If `pr_reference` is a URL**, parse them from the URL path. + URL-decode each segment for display, comparison, and JSON + payloads. When constructing REST URIs, **URL-encode each path + segment** (or preserve the already-encoded segments from the + original URL) so values containing spaces or other reserved + characters do not produce malformed requests. + - **If `pr_reference` is a bare id** (`#42`, `!123`), derive + `owner`/`repo` (GitHub) or `org`/`project`/`repoName` (ADO) from + the selected upstream remote. Recognise these forms: + - GitHub HTTPS: `https://github.com/{owner}/{repo}(.git)?` + - GitHub SSH: `git@github.com:{owner}/{repo}(.git)?` + - ADO HTTPS: `https://dev.azure.com/{org}/{project}/_git/{repo}` + - ADO SSH: `git@ssh.dev.azure.com:v3/{org}/{project}/{repo}` + - ADO legacy HTTPS: `https://{org}.visualstudio.com/{project}/_git/{repo}` + - ADO legacy SSH: `{org}@vs-ssh.visualstudio.com:v3/{org}/{project}/{repo}` + The bare id provides `pr_number` / `prId`. + - If any required field cannot be determined unambiguously, prompt + the user. Do NOT invent values. + +### Phase 2: Gather Review Threads + +Fetch all review threads from the platform. + +#### GitHub branch 1. **Read all review threads** on the PR: - Use `gh pr view {{pr_reference}} --comments` for a quick overview, but use @@ -72,6 +138,15 @@ explanatory replies, or both. - Use a GraphQL query via `gh api graphql` that includes each thread's ID, state, path, and line metadata, plus each comment's database ID, author, and body + - **Paginate exhaustively.** Both `reviewThreads` and the inner + `comments` connection paginate independently. Page outer + `reviewThreads(first: 100, after: $cursor)` until + `pageInfo.hasNextPage` is `false`. Then, for any thread whose + inner `comments.pageInfo.hasNextPage` is `true`, issue a + follow-up query keyed by the thread `id` with + `comments(first: 100, after: $commentCursor)` and repeat until + exhausted. PRs with many reviewers easily exceed 100 comments + on a single thread. - Preserve these IDs in your working notes so later action steps can post replies and resolve the correct threads @@ -87,10 +162,141 @@ explanatory replies, or both. - Understand the surrounding context (function, class, module) - Check if the code has changed since the review comment was posted -### Phase 2: Detect Contradictions - -Compare feedback across different reviewers on the same code area -or design decision: +#### Azure DevOps branch + +**Authentication.** All ADO API calls in this template use `az rest` +with `--resource 499b84ac-1321-427f-aa17-267ca6975798` (the Azure +DevOps resource ID). The user must have run `az login` once. Do NOT +omit `--resource` — without it, `az rest` may attach a token for the +wrong audience and the call will fail authorization. Do NOT instruct +the user to mint a Personal Access Token. + +1. **Resolve the repository GUID and PR id.** Parse the PR URL: + - `org`, `project`, `repoName`, `prId` from the URL path + (URL-decode `project` and `repoName`). + - Look up the repository GUID — needed by some thread endpoints: + + ```powershell + az rest --resource 499b84ac-1321-427f-aa17-267ca6975798 ` + --method GET ` + --uri "https://dev.azure.com/{org}/{project}/_apis/git/repositories/{repoName}?api-version=7.1" + ``` + + Record the returned `id` as `repoId`. + - For fork PRs, ADO targets the **target repository** for thread + operations; `repoId` from the URL above is correct. + +2. **List all PR threads.** This endpoint returns the full collection + in a single response — there is **no `$top`/`$skip` pagination** + documented for this endpoint, and comments are embedded inline + in each thread: + + ```powershell + az rest --resource 499b84ac-1321-427f-aa17-267ca6975798 ` + --method GET ` + --uri "https://dev.azure.com/{org}/{project}/_apis/git/repositories/{repoId}/pullRequests/{prId}/threads?api-version=7.1" + ``` + + Read `value[]` from the response. For each thread, record: + - `id`: the thread id (integer; required for status updates and + posting replies) + - `status`: one of `active`, `pending`, `fixed`, `wontFix`, + `closed`, `byDesign`, `unknown` (lowercase API enum values — + ADO uses `fixed`, NOT `resolved`) + - `threadContext`: file path (`filePath`), line range + (`rightFileStart` / `rightFileEnd`), and side. May be `null` + for PR-wide threads or system threads. + - `pullRequestThreadContext.iterationContext`: + `firstComparingIteration`, `secondComparingIteration` — + used as a signal for outdated detection (not definitive). + - `properties` and `comments[*].commentType` — used to identify + system threads (see step 4). + - For each comment in `comments[]`: `id` (the + `parentCommentId` for posting a reply), author display name + and unique name, content, and `commentType`. + +3. **Get the latest iteration** (used for outdated detection): + + ```powershell + az rest --resource 499b84ac-1321-427f-aa17-267ca6975798 ` + --method GET ` + --uri "https://dev.azure.com/{org}/{project}/_apis/git/repositories/{repoId}/pullRequests/{prId}/iterations?api-version=7.1" + ``` + + Record the highest `id` as `latestIteration`. + +4. **Filter out system threads.** Skip threads where any of these + are true (these are auto-generated by ADO, not human review): + - All comments have `commentType: "system"`. + - `properties` contains a `CodeReviewThreadType` of + `MergeAttempt`, `VoteUpdate`, `ReviewersUpdate`, `RefUpdate`, + or `StatusUpdate`. + + Count skipped system threads for the summary; do NOT process them. + Threads that do not match the rules above but contain no `text` + comments (only attachments, reactions, or non-text content) + should NOT be auto-skipped — flag them and ask the user. + +5. **Filter remaining threads** based on `review_threads` parameter: + - If `all pending` — include threads with status `active` (the + default state for new comments needing response). + - **Pending threads (status `pending`)**: flag and ask the user + whether to address now — the comment author marked them as + awaiting something. + - Skip `fixed`, `wontFix`, `closed`, `byDesign`, `unknown` + unless the user explicitly requests them. + - If specific threads are listed, include only those. + +6. **Detect potentially outdated threads.** ADO has no native + "outdated" status; use the conservative algorithm below and only + flag as **potentially outdated** (do not assert). + + **Skip this detection entirely for PR-wide threads** — if + `threadContext` is null, the thread has no file or lines to + verify. Pass it through unchanged to step 7. + + For file-anchored threads only, decide the source of truth for + "current file contents": + - **Preferred**: query the latest iteration's changes via + `GET .../pullRequests/{prId}/iterations/{latestIteration}/changes?api-version=7.1` + and the file content via + `GET .../items?path={filePath}&versionDescriptor.version={sourceBranch}&versionDescriptor.versionType=branch&api-version=7.1`. + - **Fallback**: the local working tree, **only** if you can + confirm `HEAD` matches the PR source branch tip at + `latestIteration` (e.g., compare commit SHA from the iterations + response against `git rev-parse HEAD`). Otherwise do NOT use + local files. + - **If you cannot verify either**, mark the thread's outdated + status as **unknown / not verified** and ask the user — do NOT + guess. + + With a verified source of truth, flag as potentially outdated + when any holds: + - The thread's `threadContext.filePath` no longer exists in the + latest iteration (deleted or renamed file). + - The thread's line range + (`threadContext.rightFileStart` / `rightFileEnd`) is outside + the current file's line count. + - `pullRequestThreadContext.iterationContext.secondComparingIteration` + is older than `latestIteration` AND the file/lines have changed + since that iteration. + + For each potentially outdated thread, ask the user whether to + address it. + +7. **Identify PR-wide (general) threads.** Threads with + `threadContext: null` that survived the system-thread filter are + genuine PR-wide discussion threads (added on the **Overview** tab + in the ADO UI). Group these separately from file-anchored threads + in the report — do NOT skip them. + +8. **Read the current code** at each file-anchored thread's location + (same as the GitHub branch, step 3). + +### Phase 3: Detect Contradictions + +This phase is platform-agnostic. Compare feedback across different +reviewers on the same code area or design decision: 1. **Group threads by location**: threads on the same file within 10 lines of each other, or threads referencing the same function @@ -106,9 +312,10 @@ or design decision: 3. **Report contradictions** with both positions and a recommended resolution. Do NOT silently pick one side — flag for the user. -### Phase 3: Analyze Each Thread +### Phase 4: Analyze Each Thread -For each actionable thread, determine the response type: +This phase is platform-agnostic. For each actionable thread, determine +the response type: 1. **If `response_mode` is `auto`**, apply these heuristics: @@ -140,19 +347,25 @@ For each thread, produce: that explains the design decision, tradeoff, or rationale. Keep it professional, concise, and technical. -### Phase 4: Output +### Phase 5: Output + +Use the **source platform's native status vocabulary** in all output. +Do NOT translate ADO statuses into GitHub terms or vice versa. #### Document Mode (`output_mode: document`) Produce the output following the `pr-comment-responses` format: -1. Thread Summary (by state) +1. Thread Summary (by state; use the platform's status table) 2. Contradiction Report -3. Per-Thread Responses (in file order) +3. Per-Thread Responses (in file order; PR-wide threads grouped at + the end on ADO) 4. Action Summary #### Action Mode (`output_mode: action`) -Execute responses with **mandatory user confirmation at every step**: +Execute responses with **mandatory user confirmation at every step**. +The orchestration is identical across platforms; only the API calls +differ. 1. **Present the full analysis** (thread summary, contradictions, per-thread responses) to the user using the document structure. @@ -169,71 +382,149 @@ Execute responses with **mandatory user confirmation at every step**: c. If confirmed, commit with a descriptive message referencing the review threads addressed, then push. -4. **For each thread with an explanation**: +4. **For each thread with an explanation, post the reply.** + For each thread: a. Show the draft reply to the user. b. Ask: "Post this reply? (yes / skip / edit)" - c. If confirmed, write the reply payload to `reply.json` and post: - ```json - { - "body": "", - "in_reply_to": - } - ``` - ``` - gh api repos/{owner}/{repo}/pulls/{pr_number}/comments \ - --method POST \ - --input reply.json - ``` - -5. **For threads that were fixed**: - a. Ask: "Resolve these threads? (yes / no)" - b. If confirmed, resolve each thread using: - ``` - gh api graphql \ - -f query='mutation($threadId: ID!) { - resolveReviewThread(input: {threadId: $threadId}) { - thread { isResolved } - } - }' \ - -F threadId="" - ``` + c. If confirmed, post using the platform-appropriate API: + + **GitHub** — write the reply payload to `reply.json` and POST: + ```json + { + "body": "", + "in_reply_to": + } + ``` + ``` + gh api repos/{owner}/{repo}/pulls/{pr_number}/comments \ + --method POST \ + --input reply.json + ``` + + **Azure DevOps Services** — POST inline (no temp file needed): + ```powershell + az rest --resource 499b84ac-1321-427f-aa17-267ca6975798 ` + --method POST ` + --uri "https://dev.azure.com/{org}/{project}/_apis/git/repositories/{repoId}/pullRequests/{prId}/threads/{threadId}/comments?api-version=7.1" ` + --headers "Content-Type=application/json" ` + --body '{ "content": "", "parentCommentId": , "commentType": "text" }' + ``` + + ADO replies MUST always include `parentCommentId` — choose the + specific comment being answered. For PR-wide (general) threads, + reply to the latest text comment in the thread (or the first + comment if the thread has only one). Do NOT omit `parentCommentId` + to post an unparented top-level comment — it changes the semantics + from "reply" to "new top-level remark" and breaks the API + contract used elsewhere in this template. + +5. **For threads that were addressed, update thread status.** + Map the user's intent to the appropriate platform-native status: + + | User intent after addressing a thread | GitHub action | ADO action | + |---|---|---| + | Code fix applied, issue addressed | resolve | set status to `fixed` | + | Explanation posted, user wants discussion closed | resolve | set status to `closed` | + | Explanation posted, leave open for reviewer reply | (no change) | leave `active` | + | Concern noted, team chooses not to act | (no change; reply explains) | set status to `wontFix` | + | Reviewer's concern is intentional design | (no change; reply explains) | set status to `byDesign` | + + For each affected thread, ask the user to confirm the intended + transition before executing. + + **GitHub** — resolve the thread: + ``` + gh api graphql \ + -f query='mutation($threadId: ID!) { + resolveReviewThread(input: {threadId: $threadId}) { + thread { isResolved } + } + }' \ + -F threadId="" + ``` + + **Azure DevOps Services** — PATCH the thread status (use the + exact lowercase enum value: `active`, `pending`, `fixed`, + `wontFix`, `closed`, `byDesign` — note ADO uses `fixed`, NOT + GitHub's `resolved`): + ```powershell + az rest --resource 499b84ac-1321-427f-aa17-267ca6975798 ` + --method PATCH ` + --uri "https://dev.azure.com/{org}/{project}/_apis/git/repositories/{repoId}/pullRequests/{prId}/threads/{threadId}?api-version=7.1" ` + --headers "Content-Type=application/json" ` + --body '{ "status": "fixed" }' + ``` 6. **Never take any action without explicit user confirmation.** If the user skips all items, produce a document-mode report instead. -### Phase 5: Handle Edge Cases +### Phase 6: Handle Edge Cases -- **No pending threads**: Report "No actionable review threads found" - and list any resolved/outdated threads for reference. +- **No actionable threads**: Report "No actionable review threads found" + and list any skipped threads (in the platform's native vocabulary — + GitHub `resolved`, ADO `fixed` / `closed` / `wontFix` / `byDesign`; + outdated / potentially outdated; ADO system threads) with counts + for reference. - **Large thread count (>20)**: Process in batches of 10. After each batch, summarize progress and ask to continue. -- **Outdated threads**: Flag these separately. Ask the user whether - to address them — the code may have already changed to address - the feedback. +- **Outdated / potentially outdated threads**: Flag these separately. + Ask the user whether to address them — the code may have already + changed to address the feedback. - **Threads on deleted files**: Skip with a note explaining the file - no longer exists. + no longer exists. On ADO, this is also a signal that the thread is + potentially outdated. +- **ADO system threads**: Always exclude from analysis; report only + the count in the summary. +- **ADO PR-wide threads**: Process them, but group them in a separate + "PR-wide threads" section in the report — they have no file/line + context. +- **Unsupported ADO host** (Server / on-prem / TFS): Stop with a + clear message; do not attempt to call APIs against an unsupported + endpoint. +- **`az rest` 401/403**: Most often caused by missing `--resource`, + expired `az login`, or insufficient permissions on the project. + Tell the user which of these to check; do NOT fall back to + recommending a PAT. ## Non-Goals - Do NOT perform a new code review — focus only on addressing existing feedback. - Do NOT modify code beyond what is needed to address review comments. -- Do NOT resolve threads without user confirmation. +- Do NOT update thread status without user confirmation. - Do NOT dismiss or ignore valid feedback — if a reviewer is correct, acknowledge it and fix it. - Do NOT take sides in contradictions — present both positions and let the user decide. - Do NOT push commits without explicit user confirmation. +- Do NOT translate or normalize statuses across platforms — use each + platform's native vocabulary. +- Do NOT instruct the user to create a Personal Access Token for ADO; + rely on `az login` + `az rest --resource`. +- Do NOT attempt to support Azure DevOps Server (on-prem) or TFS + with this template's auth path. ## Quality Checklist Before finalizing, verify: -- [ ] Every pending thread has a response (fix, explanation, or both) +- [ ] Platform was detected (or the user was asked) before any API call +- [ ] Every actionable thread has a response (fix, explanation, or both) - [ ] Contradictions across reviewers are explicitly flagged - [ ] Code fixes show before/after with sufficient context - [ ] Draft replies are professional, concise, and technical -- [ ] Resolved and outdated threads are accounted for (skipped with reason) +- [ ] All non-actionable threads (GitHub `resolved`; ADO `fixed` / + `closed` / `wontFix` / `byDesign`; outdated / potentially + outdated; ADO system threads) are accounted + for with platform-native status names and counts - [ ] In action mode: user confirmation obtained before every mutation -- [ ] Thread states (pending/resolved/outdated) are accurately reported +- [ ] Thread states are reported using the source platform's native + vocabulary (no cross-platform normalization) +- [ ] On ADO: every `az rest` invocation includes + `--resource 499b84ac-1321-427f-aa17-267ca6975798` +- [ ] On ADO: status PATCH payloads use the exact lowercase enum + values (`active`, `pending`, `fixed`, `wontFix`, `closed`, + `byDesign`) +- [ ] On ADO: reply POST bodies use `content` + `parentCommentId` + + `commentType: "text"` (NOT GitHub's `body` / `in_reply_to`) - [ ] Files modified by fixes are listed in the action summary From 41243592af6cecb90a22ef8fdeb083ce0513f4db Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Mon, 4 May 2026 13:06:42 -0600 Subject: [PATCH 2/2] fix(respond-to-pr-comments): address PR #253 review feedback - ADO reply POST switched to temp-file pattern (--body @reply.json) to handle apostrophes/newlines/backslashes in real reply text; mirrors the GitHub recipe. - pr_reference param doc clarified: URL auto-detect with git-remote fallback and ambiguity prompt (covers #42 / !123 inputs). - All shell command fences in SKILL labeled bash for clarity. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../skills/respond-to-pr-comments/SKILL.md | 28 ++++++++++----- templates/respond-to-pr-comments.md | 36 ++++++++++++------- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/.github/skills/respond-to-pr-comments/SKILL.md b/.github/skills/respond-to-pr-comments/SKILL.md index bf4d531..702ffd0 100644 --- a/.github/skills/respond-to-pr-comments/SKILL.md +++ b/.github/skills/respond-to-pr-comments/SKILL.md @@ -171,18 +171,18 @@ query($threadId: ID!, $commentCursor: String) { Run `az login` once. Then: 1. Resolve `repoId`: - ``` + ```bash az rest --resource 499b84ac-1321-427f-aa17-267ca6975798 --method GET \ --uri "https://dev.azure.com/{org}/{project}/_apis/git/repositories/{repoName}?api-version=7.1" ``` 2. List threads (returns full `value[]` in one response — no `$top`/`$skip` pagination on this endpoint; comments are embedded): - ``` + ```bash az rest --resource 499b84ac-1321-427f-aa17-267ca6975798 --method GET \ --uri "https://dev.azure.com/{org}/{project}/_apis/git/repositories/{repoId}/pullRequests/{prId}/threads?api-version=7.1" ``` 3. Get latest iteration (for outdated detection): - ``` + ```bash az rest --resource 499b84ac-1321-427f-aa17-267ca6975798 --method GET \ --uri "https://dev.azure.com/{org}/{project}/_apis/git/repositories/{repoId}/pullRequests/{prId}/iterations?api-version=7.1" ``` @@ -311,7 +311,7 @@ Execute with **mandatory user confirmation at every step**. - Post if confirmed: **GitHub:** - ``` + ```bash cat > reply.json <<'EOF' { "body": "", "in_reply_to": } EOF @@ -325,11 +325,19 @@ Execute with **mandatory user confirmation at every step**. comment (or the first comment if the thread has only one). Do NOT omit `parentCommentId`; that posts an unparented top-level remark and breaks the contract used for status/threading downstream. - ``` + + Always write the reply body to a temp file and pass `--body @file` + — never inline as `--body '...'`. Real reply text contains + apostrophes, newlines, and backslashes that break shell quoting in + both bash and PowerShell. + ```bash + cat > reply.json <<'EOF' + { "content": "", "parentCommentId": , "commentType": "text" } + EOF az rest --resource 499b84ac-1321-427f-aa17-267ca6975798 --method POST \ --uri "https://dev.azure.com/{org}/{project}/_apis/git/repositories/{repoId}/pullRequests/{prId}/threads/{threadId}/comments?api-version=7.1" \ --headers "Content-Type=application/json" \ - --body '{ "content": "", "parentCommentId": , "commentType": "text" }' + --body @reply.json ``` 4. **Update thread status** — always confirm each transition with @@ -344,14 +352,16 @@ Execute with **mandatory user confirmation at every step**. | Intentional design | (no change) | `byDesign` | **GitHub** — resolve: - ``` + ```bash gh api graphql -f query='mutation($threadId: ID!) { resolveReviewThread(input: {threadId: $threadId}) { thread { isResolved } } }' -F threadId="" ``` - **ADO** — PATCH with exact lowercase enum: - ``` + **ADO** — PATCH with exact lowercase enum (the body is a fixed + small JSON literal with no user content, so inlining `--body '...'` + is safe here): + ```bash az rest --resource 499b84ac-1321-427f-aa17-267ca6975798 --method PATCH \ --uri "https://dev.azure.com/{org}/{project}/_apis/git/repositories/{repoId}/pullRequests/{prId}/threads/{threadId}?api-version=7.1" \ --headers "Content-Type=application/json" \ diff --git a/templates/respond-to-pr-comments.md b/templates/respond-to-pr-comments.md index b50deac..7011ce8 100644 --- a/templates/respond-to-pr-comments.md +++ b/templates/respond-to-pr-comments.md @@ -16,7 +16,7 @@ protocols: - guardrails/operational-constraints format: pr-comment-responses params: - pr_reference: "Pull request to respond to — full URL (GitHub or Azure DevOps Services), PR number (e.g., #42 for GitHub), or PR id (e.g., !123 for ADO). Platform is auto-detected from the URL." + pr_reference: "Pull request to respond to — full URL (GitHub or Azure DevOps Services), PR number (e.g., #42 for GitHub), or PR id (e.g., !123 for ADO). Platform is auto-detected from the URL when given; otherwise inferred from `git remote -v` of the current repo. If both signals are absent or ambiguous, the workflow prompts you to pick rather than guessing." review_threads: "Review feedback to address — 'all pending', specific thread URLs, or pasted comments" codebase_context: "What this code does, relevant architecture, design decisions that inform responses" response_mode: "How to respond per-thread — 'auto' (heuristic), 'fix' (code changes), or 'explain' (rationale)" @@ -401,13 +401,23 @@ differ. --input reply.json ``` - **Azure DevOps Services** — POST inline (no temp file needed): - ```powershell - az rest --resource 499b84ac-1321-427f-aa17-267ca6975798 ` - --method POST ` - --uri "https://dev.azure.com/{org}/{project}/_apis/git/repositories/{repoId}/pullRequests/{prId}/threads/{threadId}/comments?api-version=7.1" ` - --headers "Content-Type=application/json" ` - --body '{ "content": "", "parentCommentId": , "commentType": "text" }' + **Azure DevOps Services** — write the reply payload to `reply.json` + and POST. Always use a temp file (not an inlined `--body '...'` + string): real reply text contains apostrophes, newlines, and + backslashes that break shell quoting in both bash and PowerShell. + ```json + { + "content": "", + "parentCommentId": , + "commentType": "text" + } + ``` + ```bash + az rest --resource 499b84ac-1321-427f-aa17-267ca6975798 \ + --method POST \ + --uri "https://dev.azure.com/{org}/{project}/_apis/git/repositories/{repoId}/pullRequests/{prId}/threads/{threadId}/comments?api-version=7.1" \ + --headers "Content-Type=application/json" \ + --body @reply.json ``` ADO replies MUST always include `parentCommentId` — choose the @@ -447,11 +457,11 @@ differ. exact lowercase enum value: `active`, `pending`, `fixed`, `wontFix`, `closed`, `byDesign` — note ADO uses `fixed`, NOT GitHub's `resolved`): - ```powershell - az rest --resource 499b84ac-1321-427f-aa17-267ca6975798 ` - --method PATCH ` - --uri "https://dev.azure.com/{org}/{project}/_apis/git/repositories/{repoId}/pullRequests/{prId}/threads/{threadId}?api-version=7.1" ` - --headers "Content-Type=application/json" ` + ```bash + az rest --resource 499b84ac-1321-427f-aa17-267ca6975798 \ + --method PATCH \ + --uri "https://dev.azure.com/{org}/{project}/_apis/git/repositories/{repoId}/pullRequests/{prId}/threads/{threadId}?api-version=7.1" \ + --headers "Content-Type=application/json" \ --body '{ "status": "fixed" }' ```