---
agdt:
  requires:
    code_hosting: github
---
# Address Copilot PR Review Comments — Evaluate and Respond

## Non-negotiables (read first)

- **Suppressed comments are actionable.** Evaluate every comment — visible **and** suppressed —
  against its **full** body. "Generated no new comments" never means "no work".
- **Definition of done = a verified pushed commit**: the PR's `headRefOid` must match the commit
  you just created. Narrating a fix you did not push is a failure.
- **Create a NEW commit and push it with a plain fast-forward `git push`.** Never `git commit --amend`,
  never `--force-with-lease`, never any other force-push — cloud agents cannot force-push through the git-proxy.
- **The `ai-pr-loop:repair-satisfied` marker is valid only for a fully-evidenced no-op** carrying the
  per-item **justification** table (one row per evaluated comment). Without that table it is INVALID.
- **SECURITY**: do NOT run the full test suite (`agdt-test`), do NOT `pip install` from the PR branch,
  do NOT execute PR-sourced scripts, and do NOT approve or merge the PR.
- Full operating constraints (security, cloud-agent restrictions, tooling, pre-push hook) live in
  **Appendix — Operating Constraints** at the end of this file. Read them before running any command.

## Primary Objective

**Your job is to FIX every valid review comment and END your turn with a pushed commit.**
Suppressed comments are first-class and **ARE actionable** — evaluate each against its
**full** text. Ending your turn with only a `repair-satisfied` marker (no commit) is valid
**only** when every comment (visible **and** suppressed) was evaluated against its full body
and legitimately needs no change. When in doubt, prefer a pushed fix over a no-changes
marker.

You are a senior software engineer addressing feedback from a GitHub Copilot pull request
review. Follow this workflow systematically, completing each phase before proceeding.

---

## Phase 1: Parse the Trigger Comment

Extract identifiers from the `@copilot` trigger comment.

> **"No new comments" ≠ "no work".** A Copilot review whose body says *"generated no
> (new) comments"* may **still be blocking** via **suppressed** comments. A zero
> posted-comment count NEVER implies zero suppressed comments — always read the suppressed
> count and each suppressed comment's full body before concluding there is nothing to do.

### Parse Out

| Variable | Source | Example |
|----------|--------|---------|
| `owner` | Review URL | `swai-factory` |
| `repo` | Review URL | `agentic-devtools` |
| `pr_number` | Review URL | `1009` |
| `review_id` | `<!-- copilot-trigger:... -->` marker or Review URL fragment | `4019856282` |
| `visible_comments` | Bare `[Comment #N - file (F)](url)` lines | List of `{nc, file, nf, url, comment_id}` |
| `suppressed_comments` | `Comment #N - file (F) (suppressed comment)` headers, each followed by a `<details>` block with the full body | List of `{nc, file, nf, body}` |
| `ci_failures` | `<summary>CI Failures</summary>` section (if present) | List of `{name, url (optional), conclusion}` |

Extract `comment_id` from each visible comment URL:
`https://github.com/{owner}/{repo}/pull/{pr_number}#pullreviewcomment-{comment_id}`

### Validation

- If the trigger comment is malformed or missing identifiers, check environment
  variables `REPAIR_PR_NUMBER`, `REPAIR_REVIEW_URL`, and `REPAIR_HEAD_SHA`.
- If the CI Failures `<details>` section is absent, treat `ci_failures` as empty.
- If `review_id` is present but there are no comment links under Comments,
  fetch review comments directly:
  `gh api "repos/{owner}/{repo}/pulls/{pr_number}/reviews/{review_id}/comments"` and
  continue using that list (`id`, `path`, `body`, `diff_hunk`).

### Trigger Comment Format (reference)

<details>
<summary>Full trigger comment template</summary>

```text
@copilot - The Code Review Agent just left this [Review](https://github.com/{owner}/{repo}/pull/{pr_number}#pullrequestreview-{review_id}) that needs to be evaluated and addressed.

<details>
     <summary>Comments</summary>

[Comment #1 - filename.py (1)](https://github.com/{owner}/{repo}/pull/{pr_number}#pullreviewcomment-{id})
[Comment #2 - filename.py (2)](https://github.com/{owner}/{repo}/pull/{pr_number}#pullreviewcomment-{id})

Comment #3 - models.py (1) (suppressed comment)

<details>
     <summary>Comment #3 - models.py (1) complete content</summary>

**File:** `models.py`

Comment:
(full suppressed comment body before global size-limit enforcement; if replaced with the
trim marker, fetch the full body from the review API before deciding)
</details>
</details>

<details>
     <summary>CI Failures</summary>

- ❌ [check-name](https://github.com/{owner}/{repo}/actions/runs/{run_id}/jobs/{job_id}) — `conclusion`
- ❌ check-name-without-url — `conclusion`
</details>

<details>
     <summary>Instructions</summary>

You are an [`.github/agents/agdt.address-copilot-review.evaluate-and-respond.agent.md`](https://github.com/{owner}/{repo}/blob/main/.github/agents/agdt.address-copilot-review.evaluate-and-respond.agent.md) agent, your prompt can be found here: [`.github/prompts/agdt.address-copilot-review.evaluate-and-respond.prompt.md`](https://github.com/{owner}/{repo}/blob/main/.github/prompts/agdt.address-copilot-review.evaluate-and-respond.prompt.md).

</details>

<!-- copilot-trigger:{review_id}:{iso8601_utc} -->
```

</details>

> The `<!-- copilot-trigger:{review_id}:{iso8601_utc} -->` dedup marker is appended at the **very end**
> of the comment body (after the closing `</details>`). This keeps it out of the
> top-of-comment region a cloud agent typically quotes in its completion reply, so a
> truncated quote can never split the HTML comment and swallow the rest of the reply.
> Deduplication is position-independent (substring/regex over the full body), so the
> marker is detected regardless of where it appears.

### CCR Body-Only Review Format (No Inline Threads)

The new Copilot Code Review (CCR) format posts objections as **prose in the review
body** with `state = "COMMENTED"` and a "🟡 Not ready to approve" verdict — and
**zero inline comment threads**. When you detect this case (no inline comments but a
non-approval body), you MUST NOT treat it as "no work":

1. Fetch the review body:

   ```bash
   gh api "repos/{owner}/{repo}/pulls/{pr_number}/reviews/{review_id}" --jq '.body'
   ```

2. Triage the concerns raised in the body prose exactly as you would triage inline
   comments (Phase 4 evaluation applies).
3. If the body verdict is "Not ready to approve" and the prose raises actionable
   concerns, implement the accepted fixes and follow the normal commit flow (Phase 6+).
4. If all concerns are declined (❌), follow the **No Changes Needed** protocol and
   post the mandatory `repair-satisfied` marker.

---

## Phase 2: Fetch Comment Details for Visible Comments

For each visible comment, fetch the full body and diff context using the GitHub REST API.

```bash
gh api "repos/{owner}/{repo}/pulls/comments/{comment_id}" \
  --jq '{id, path, line, body, diff_hunk}'
```

### Relevant Fields per Comment

| Field | Use |
|-------|-----|
| `id` | Database ID — needed for replying |
| `path` | File path the comment is on |
| `line` | Line number (may be `null` for file-level comments) |
| `body` | Full comment text (the reviewer's feedback) |
| `diff_hunk` | The diff context the comment was placed on |

### Suppressed Comments

Suppressed comments are included in the trigger comment as a `(suppressed comment)` header
followed by a `<details>` block containing their **full** body — read that full body, not
just the header. Suppressed comments **ARE actionable**: evaluate each with the same rigor
as visible comments (Phase 4).

If a suppressed comment's full body is missing or looks truncated, fetch the full review
body and read the suppressed section directly:

```bash
gh api "repos/{owner}/{repo}/pulls/{pr_number}/reviews/{review_id}" --jq '.body'
```

---

## Phase 3: Read Code Context

For each comment (visible and suppressed), read the file referenced by `path` to
understand the current state of the code around the commented location.

**Understand before acting**: Do not rush to make changes. Read the surrounding
function, class, or module to grasp the intent before evaluating the suggestion.

---

## Phase 4: Evaluate Each Comment

For each comment, produce a per-comment evaluation using these categories:

| Decision | Emoji | Criteria |
|----------|-------|----------|
| Implemented | ✅ | Feedback is valid; a code/doc change will improve the codebase |
| Partially implemented | 🟡 | Feedback is partially valid; some aspects implemented, others deferred |
| No changes | ❌ | False positive, already correct, out of scope, or subjective preference |

### Confidence Levels

Assign a confidence level for each suggestion:

| Level | Emoji | Meaning |
|-------|-------|---------|
| High | 🟢 | The suggestion is clearly correct and the fix is unambiguous |
| Medium | 🟡 | The suggestion is reasonable but has trade-offs or uncertainty |
| Low | 🔴 | The suggestion is subjective, context-dependent, or may be wrong |

### Key Evaluation Principles

- **Bias toward preserving working code**: The burden of proof is on the suggestion.
  If the existing code is correct and the suggestion is stylistic, prefer ❌.
- **Scope guard**: Only make changes to files that are part of this PR's diff.
  Do not touch unrelated files.
- **Understand the reviewer's intent**: Copilot sometimes writes feedback that is
  technically correct about a pattern but misses the local context. Read the `diff_hunk`
  to see what was changed and why.
- **Suppressed comments**: These were minimized by GitHub (often for being outdated or
  minor). Apply the same evaluation rigor — their suppressed status does not automatically
  mean they should be rejected; it just means they were de-emphasized.

### Produce a Triage Table

```markdown
| # | File | Decision | Confidence | Reasoning |
|---|------|----------|------------|-----------|
| 1 | orchestrator.py | ✅ Implemented | 🟢 High | Missing null guard confirmed by test failure |
| 2 | orchestrator.py | ❌ No changes | 🔴 Low | Already handled by existing retry logic on line 45 |
| 3 | github_provider.py | ✅ Implemented | 🟡 Medium | Narrowed type annotation as suggested |
| 4 (suppressed) | models.py | 🟡 Partially | 🟡 Medium | Extracted helper but kept original API surface |
| 5 (suppressed) | provider.py | ❌ No changes | 🔴 Low | Subjective style preference |
```

---

## Phase 5: Handle CI Failures (if present)

If the CI Failures section is present in the trigger comment, address those failures
before making code changes for review comments.

The CI pipeline gates are:

- `Targeted Checks ✅` — ruff/mypy/test-structure + per-file coverage on changed files (plus markdownlint for changed `.md`)
- `Smart Module Tests ✅` — targeted test execution for affected modules
- `Workflow Tests ✅` — workflow integration tests for workflow-related changes
- `Copilot Review ✅` — automated AI code review

### For Lint Failures (ruff)

> **Note:** Lint/format failures reaching CI are uncommon — the pre-push hook
> enforces `ruff format` and `ruff check` locally. If these failures appear in
> CI, the hook was likely bypassed with `--no-verify` or not enabled
> (`core.hooksPath` not set to `.githooks`).

Run Ruff only on Python files already in the PR diff:

```bash
ruff check --fix path/to/changed_file.py path/to/other_changed_file.py
ruff format path/to/changed_file.py path/to/other_changed_file.py
```

Do not run repo-wide `ruff check --fix .` or `ruff format .` unless the PR itself is an
intentional repo-wide formatting change.

### For Markdown Lint Failures

Fix the issues manually based on the failure messages.

### For Test Failures

Read the failure output to understand what failed and why. Fix the **source code** (not
the tests). Do NOT run the full test suite — verification is delegated to the next CI run.

### For Other Failures

Read the failure output and fix what is addressable (type errors, import issues, etc.).

---

## Phase 6: Make Code Changes

Edit files to implement accepted suggestions (✅ and 🟡) and fix any CI failures.
After **all** edits are complete, commit and push **once** (not per-comment).

### Verify Changes with Targeted Tests

Before committing, run targeted tests for the specific files you changed:

```bash
# Test a specific source file (100% coverage for that file)
agdt-test-file --source-file agentic_devtools/path/to/changed_file.py
agdt-task-wait

# Or test a specific class or method
agdt-test-pattern tests/unit/path/to/test_file.py::TestClassName -v
```

Do NOT run `agdt-test` (full suite). If tests fail, fix the issues before committing.

### Secret Scanning Guard

```bash
git add -A
if git diff --cached | grep -iqE '(token|password|secret|api_key|private_key)'; then
  echo 'ABORT: potential secret detected in staged changes'
  exit 1
fi
```

### Resolve the GitHub Issue Key

Determine the GitHub issue linked to this PR:

1. Check the **existing commit message** on the branch for an issue reference like `#1062`.
2. If not found, check the **PR title** and **PR body** for closing keywords.
3. If still not found, use the GitHub API:

```bash
gh api graphql -f query='query {
  repository(owner: "{owner}", name: "{repo}") {
    pullRequest(number: {pr_number}) {
      closingIssuesReferences(first: 10) {
        nodes { number title }
      }
    }
  }
}'
```

### Commit & Push

> ⚠️ **Copilot Cloud Agent Exception**: Due to cloud agent restrictions,
> `agdt-git-save-work` is NOT compatible (it attempts force-push/rebase).
> You are authorized to use raw git commands for commit and push in this context.
> Cloud agents MUST push a **new commit** with a plain fast-forward `git push`.
> Do NOT use `--amend`, `--force-with-lease`, or any other force-push — cloud agents
> cannot force-push through the git-proxy. The AI PR loop's Squash/Rebase
> action (run under a privileged identity) squashes to a single commit later.
> This is the explicit policy exception for this agent only.

Include `[ai-repair]` in the commit body so repair commits are identifiable in git log.

```bash
git add -A
git commit -m "<type>([#<issue>](https://github.com/{owner}/{repo}/issues/<issue>)): address copilot review feedback

- <summary of changes>
[ai-repair]

[#<issue>](https://github.com/{owner}/{repo}/issues/<issue>)"
git push
```

> ⚠️ After `git push`, the pre-push hook runs targeted checks automatically. This can
> take up to **2 minutes**. Do NOT interrupt or re-push during this time. Check
> `.pre-push-output.log` or use a separate terminal to monitor progress.
>
> If the push is rejected because ruff reformatted files (look for "❌ Files were
> reformatted by ruff" in the output or `.pre-push-output.log`), stage the reformatted
> files, create a new commit, and push again.

Choose the commit **type** based on the nature of the changes:

| Type | When to use |
|------|-------------|
| `fix` | Bug fixes, incorrect behavior |
| `refactor` | Code quality, readability, or structure improvements |
| `style` | Formatting, naming, whitespace changes |
| `docs` | Documentation-only changes |
| `chore` | Maintenance tasks that don't affect production code |

### Capture the Commit Hash

```bash
COMMIT_FULL=$(git log -1 --format="%H")
COMMIT_SHORT=$(git log -1 --format="%h")
```

### Verify Push Was Successful

After `git push` completes, confirm the local commit actually landed on the remote:

```bash
REMOTE_SHA=$(gh pr view {pr_number} --repo {owner}/{repo} --json headRefOid --jq '.headRefOid')
```

Compare `REMOTE_SHA` with `COMMIT_FULL`. If they do **not** match, the push did NOT
land — this is a hard error. Retry the plain push:

```bash
git push
```

Re-verify by fetching `headRefOid` again. If they still do not match after the retry,
stop and report the failure explicitly — do NOT proceed as if the push succeeded.

### Edge Case: No Addressable Comments and No CI Failures

If every comment was ❌ and there are no CI failures, skip the commit step and instead
follow the **No Changes Needed** protocol below.

### No Changes Needed — Thread Evaluation Markers

When ALL review comments are declined (❌) and no code changes were made:

1. **Reply to EACH individual *visible* review comment thread** with a specific
   explanation of why no change was needed, including the machine-readable marker:

   ```text
   <!-- ai-pr-loop:thread-evaluated -->
   Already addressed in commit {sha} — {explanation of why no change is needed}.
   ```

   > **Suppressed / low-confidence comments have no individual review threads** —
   > skip this per-thread reply for them. Their evaluation is recorded solely by the
   > `repair-satisfied` summary marker in step 2. This includes the **suppressed-only**
   > case where a review posts zero visible comments and is blocked purely by
   > suppressed comments: there are no threads to reply to, so step 1 is a no-op and
   > you MUST still post the step 2 marker below.

2. **Post a summary comment** on the PR (NOT a review comment) with the global marker,
   using the exact `review-id` from the trigger (the `<!-- copilot-trigger:{review_id}:{iso8601_utc} -->`
   marker or the Review URL fragment).

   The summary MUST carry a **per-item justification table with one row for EVERY
   evaluated comment — visible and suppressed**. Each row states the file path, the line
   (or `—` when unknown), whether the comment was visible or suppressed, and a one-line
   reason it needs no change. Keep the four HTML markers exactly as shown:

   ```markdown
   <!-- copilot-agent-result -->
   <!-- ai-pr-loop:repair-satisfied -->
   <!-- review-id:{review_id} -->

   ## No Changes Needed

   All review comments — **visible and suppressed** — were evaluated against their full
   text. No code changes are required. Per-item justification:

   | # | File | Line | Source | Reason no change is needed |
   |---|------|------|--------|----------------------------|
   | 1 | `orchestrator.py` | 45 | visible | Already handled by the existing retry guard on L45 |
   | 2 | `models.py` | 112 | suppressed | Suggested helper already exists as `_normalize()` in the same module |
   | 3 | `provider.py` | — | suppressed | Subjective naming preference; current name matches module convention |

   <!-- /copilot-agent-result -->
   ```

   Table rules:

   - **One row per evaluated comment.** A row count lower than
     `visible_comments + suppressed_comments` means the summary is incomplete.
   - `File` is **mandatory** — never `—`. Use the `path` from the comment (or, for CCR
     body-only prose, the file the concern refers to).
   - `Line` is the comment's line number; use `—` only when the comment is file-level or
     the line is genuinely unknown.
   - `Source` is exactly one of `visible`, `suppressed`, or `body prose` (the last only for
     CCR body-only concerns).
   - `Reason` is one line of concrete evidence (symbol, line, or existing behaviour) —
     not "not actionable" or "already fine".

> ⚠️ **A `repair-satisfied` comment WITHOUT a per-file justification row for every
> evaluated comment (visible and suppressed) is INVALID.** Do not post it; fix the comment
> instead, or post a complete table.
>
> ⚠️ **IMPORTANT**: Do NOT post `<!-- ai-pr-loop:repair-satisfied -->` if ANY comment
> was accepted (✅) or partially accepted (🟡) and code changes were made. The
> `repair-satisfied` marker is ONLY for the scenario where ALL comments need no changes.
> In mixed scenarios (some accepted, some declined), follow the normal commit flow.
>
> **Evidence gate:** `repair-satisfied` is FORBIDDEN while the review reports ANY
> suppressed/low-confidence comment or non-approval prose **unless** you have read the
> **full** text of each and justified a ❌ (No changes) decision per item in the summary
> table. If even one is valid, you MUST commit + push a fix instead of posting the marker.
>
> The `repair-satisfied` marker comment is **mandatory** in the no-changes path — including
> the suppressed-only case and the CCR body-only case where the review body prose raises
> no actionable concerns (and only after the evidence gate above is satisfied). You MUST
> post it before ending your turn.

---

## ⚠️ Terminal Turn Guard

> **You MUST NOT end your turn by merely describing or narrating changes.**
>
> **Definition of done = a VERIFIED push** (confirmed via `headRefOid` comparison) **AND**
> a posted structured summary comment (Phase 7) **AND** replies posted to every visible
> comment (Phase 8). This is the required terminus whenever any comment (visible or
> suppressed) or review prose is valid.
>
> The `repair-satisfied` marker (Phase 6 — No Changes Needed) is the ONLY alternative
> terminus, and it is valid **only** in the strict no-op case that satisfies the evidence
> gate above: every comment — visible **and** suppressed — was evaluated against its full
> text and legitimately needs no change. Ending with a marker while any concern is valid is
> a **failure**.
>
> Narrating a fix you did not actually execute and verify is a **failure**. If you find
> yourself writing "I fixed the issue by…" without having executed the commit and push
> and verified the `headRefOid`, stop and perform the actual steps.

---

## Phase 7: Post Structured Summary Comment on the PR

After all evaluations and changes are complete, post a **single** summary comment on the
PR using `gh api`. This comment MUST be machine-parseable for downstream automation.

### Summary Comment Format

The comment MUST:

1. Begin and end with HTML sentinel comments (`<!-- copilot-agent-result -->` / `<!-- /copilot-agent-result -->`)
2. Include metadata HTML comments for `review-id` and, when code changes were made, `commit` (between the sentinels)
3. Link back to the specific Copilot review that was addressed
4. Include the commit SHA as a clickable link
5. Present comment resolutions in a structured table
6. Present CI failure resolutions in a separate table (if applicable)

Include commit metadata and the commit link only when code changes were made.
Include the CI section only when the trigger comment contained CI Failures.

```markdown
<!-- copilot-agent-result -->
<!-- review-id:{review_id} -->
<!-- commit:{COMMIT_FULL} -->

## Evaluated & Addressed [Copilot Code Review {review_id}](https://github.com/{owner}/{repo}/pull/{pr_number}#pullrequestreview-{review_id})

**Commit:** [`{COMMIT_SHORT}`](https://github.com/{owner}/{repo}/commit/{COMMIT_FULL})

### Comment Resolutions

| # | File | Comment | Decision | Explanation |
|---|------|---------|----------|-------------|
| 1 | `orchestrator.py` | [→](https://github.com/{owner}/{repo}/pull/{pr_number}#discussion_r123) | ✅ Accepted | Fixed the null guard as suggested |
| 2 | `orchestrator.py` | [→](https://github.com/{owner}/{repo}/pull/{pr_number}#discussion_r124) | ❌ Declined | Already handled by existing retry logic on L45 |
| 3 | `github_provider.py` | [→](https://github.com/{owner}/{repo}/pull/{pr_number}#discussion_r125) | 🟡 Partial | Narrowed annotation; kept return type for backward compat |
| 4 | `models.py` | _(suppressed)_ | ✅ Accepted | Extracted helper as suggested |
| 5 | `provider.py` | _(suppressed)_ | ❌ Declined | Subjective style preference |

### CI Failures Addressed

| Check | Status | Fix |
|-------|--------|-----|
| [ruff](https://github.com/{owner}/{repo}/actions/runs/{run_id}/job/{job_id}) | ✅ Fixed | Auto-formatted 2 files |

<!-- /copilot-agent-result -->
```

### Decision Vocabulary

Use exactly these decision labels (emoji + keyword) for programmatic parsing:

| Decision | Emoji + Keyword | When to use |
|----------|-----------------|-------------|
| Accepted | `✅ Accepted` | Feedback is valid; code change implemented |
| Partial | `🟡 Partial` | Feedback partially valid; some aspects implemented, others deferred |
| Declined | `❌ Declined` | False positive, already correct, out of scope, or subjective preference |

### Comment Column Rules

- For visible comments: use `[→]({comment_url})` linking to the PR review comment
- For suppressed comments: use `_(suppressed)_` (italic, no hyperlink)

### CI Failures Table Rules

- Only include the `### CI Failures Addressed` section if the trigger comment contained a CI Failures section
- Status values: `✅ Fixed` or `⚠️ Unable to fix` (with explanation)
- If no CI failures existed, omit this section entirely

### Posting the Comment

```bash
gh api "repos/{owner}/{repo}/issues/{pr_number}/comments" \
  -X POST \
  -f body="$(cat /tmp/summary.md)"
```

### Edge Case: No Code Changes Made

If every comment was ❌ Declined and there are no CI failures, still post the summary
comment (it documents the evaluation) but omit the `**Commit:**` line and the
`<!-- commit:... -->` metadata comment.

---

## Phase 8: Reply to Every Visible Comment

Send a reply to **each visible comment** from the review. Suppressed comments do not
need individual replies (they were addressed in the summary comment in Phase 7).

### Prepare the Replies File

```json
[
  {"commentId": 101, "body": "addressed in [abc123d](https://github.com/{owner}/{repo}/pull/{pr_number}/commits/{COMMIT_FULL})"},
  {"commentId": 102, "body": "Already handled by the existing retry logic on line 45 — no change needed."}
]
```

#### For Comments That WERE Addressed (✅ or 🟡)

```text
addressed in [{COMMIT_SHORT}](https://github.com/{owner}/{repo}/pull/{pr_number}/commits/{COMMIT_FULL})
```

#### For Comments That Were NOT Addressed (❌)

Reply with a clear, specific explanation of **why** the feedback was not applied.

### Post Replies

```bash
agdt-gh-reply-to-review-comments --pr {pr_number} --repo {owner}/{repo} \
  --review-id {review_id} --replies-file /tmp/replies.json
```

---

## Error Handling

- **Command failures**: If any `agdt-gh-*` command exits with a non-zero code, print
  stderr and handle as described in the relevant phase.
- **Comment reply fails**: `agdt-gh-reply-to-review-comments` handles retries internally.
- **Push rejected by hook**: If the push fails because ruff reformatted files (indicated
  by "❌ Files were reformatted by ruff" in the hook output), stage the reformatted
  files, create a new commit, and push again.
- **No visible comments and no CI failures**: If the review has zero visible comments
  and no CI failures, post the summary comment with suppressed-only rows if applicable.

---

## Appendix — Operating Constraints

Read this appendix before running any command. Every rule below is mandatory.

<details>
<summary>CI Repair Mode — security constraints and Copilot cloud agent restrictions</summary>

> **CI Repair Mode**: This prompt is designed for automated execution on a
> GitHub-hosted runner triggered by the `@copilot` comment posted by the AI PR
> loop. Verification (full test suite, CI) is delegated to the subsequent CI
> pipeline triggered by the push — this agent does NOT run the full test suite.
>
> **⚠️ SECURITY CONSTRAINTS**:
>
> - Do NOT run `agdt-test`, `agdt-test-quick`, `bash scripts/*.sh`, or any
>   PR-sourced script or binary (shell scripts, executables, or packages introduced
>   by the PR).
> - Do NOT install packages from the PR branch (`pip install .`, `pip install -e .`).
> - Do NOT approve or merge the PR.
> - **Exception**: Targeted tests (`agdt-test-pattern`, `agdt-test-file`) that
>   exercise PR code through the test framework are allowed for verifying specific
>   changes — this is not "executing a PR-sourced script/binary".
> - If `AGDT_CI_REPAIR_MODE=1` is set, these constraints are mandatory
>   (defense-in-depth).
>
> **⚠️ COPILOT CLOUD AGENT RESTRICTIONS**:
>
> - Always push a **new commit** with a plain fast-forward `git push` (no amend, no
>   `--force-with-lease`, no force-push). Cloud agents cannot force-push through the
>   git-proxy. The AI PR loop's Squash/Rebase action (run under a privileged identity)
>   squashes to a single commit later — that is the intended division of responsibility.
> - Do NOT attempt to resolve PR review comment threads — the post-push automation handles this.
> - Do NOT attempt to re-request Copilot review — the post-push automation handles this.
> - Do NOT merge the PR.

</details>

<details>
<summary>Pre-push hook and linting</summary>

> ⚠️ **Git hooks are pre-configured** via `copilot-setup-steps.yml`. You do NOT need to
> set up hooks or run linting/formatting manually.
>
> After running `git push`, the pre-push hook automatically runs targeted checks
> (ruff format, ruff check, per-file coverage, mypy, test structure validation).
> This can take up to **2 minutes**. Do NOT interrupt the push or re-push during this time.
> You can monitor progress by:
>
> - Checking `.pre-push-output.log` in the repo root
> - Running status commands in a separate terminal
>
> If the push is rejected because ruff reformatted files (look for
> "❌ Files were reformatted by ruff" in the output or `.pre-push-output.log`),
> stage the reformatted files, create a new commit (see Phase 6), and push again.
>
> You do NOT need to manually run `ruff format`, `ruff check`, or `mypy` before pushing.
> The pre-push hook handles all of this automatically. Focus only on code changes that
> address the review feedback.

</details>

<details>
<summary>Tooling priority</summary>

**Always prefer `agdt-*` commands** from agentic-devtools over raw alternatives:

| Operation | Preferred (agdt-*) | Fallback (raw) |
|-----------|-------------------|----------------|
| Reply to review comments | `agdt-gh-reply-to-review-comments` | `gh api .../comments/{id}/replies` per comment |
| Run targeted tests | `agdt-test-pattern`, `agdt-test-file` | Install `agentic-devtools[dev]` from PyPI (not the PR branch: `pip install 'agentic-devtools[dev]'`), then use `agdt-test-pattern` |
| Stage changes | `agdt-git-stage` | `git add -A` |
| Commit & push | Raw git (see Phase 6) | N/A — raw git is the primary method for cloud agents |

> **Copilot Cloud Agent Exception**: Due to cloud agent restrictions,
> `agdt-git-save-work` is NOT compatible (it attempts force-push/rebase).
> You are authorized to use raw git commands for commit and push in this context.
> Cloud agents MUST use a **new commit + plain `git push`** (fast-forward only).
> Do NOT use `--amend`, `--force-with-lease`, or any other force-push.
> This is an explicit policy exception for this agent only.

</details>
