Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
14a3e43
feat(pr-review): refactor review-pr.md to thin orchestrator (~199 lines)
orioltf May 12, 2026
355f77d
feat(pr-review): implement Pre-PR mode — local branch diff review wit…
orioltf May 12, 2026
0a62b47
feat(pr-review): add compact structured output contract to sub-agent …
orioltf May 12, 2026
95de3f3
chore(pr-review): populate [Unreleased] CHANGELOG entries for orchest…
orioltf May 12, 2026
f615297
docs(pr-review): update CLAUDE.md for orchestrator split architecture
orioltf May 12, 2026
73ac451
chore(pr-review): bump to v1.0.0 — orchestrator split major release
orioltf May 12, 2026
9c332cc
chore(triage): resolve issue 07 — version bump and CHANGELOG
orioltf May 12, 2026
c23638b
fix: formatting
orioltf May 13, 2026
e843e78
fix(pr-review): convert static imports to dynamic in agent prompts an…
orioltf May 13, 2026
3119400
feat(pr-review): port re-review hunk parser to Node and use TMPDIR fo…
orioltf May 13, 2026
0d027f6
refactor(pr-review): trim review-pr.md orchestrator to 200 lines
orioltf May 13, 2026
27081d9
fix(pr-review): surface silent failures in ADO read/write and partial…
orioltf May 13, 2026
fc57ae6
docs(pr-review): align inline cross-refs and clarify input contracts …
orioltf May 13, 2026
82819e4
chore(inbox): capture deferred follow-ups from PR #29 review
orioltf May 13, 2026
4a98883
fix(pr-review): replace @ts-ignore with @ts-expect-error in mode-dete…
orioltf May 13, 2026
042da64
fix(pr-review): address Copilot review comments K1, K2, K4, K5
orioltf May 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
245 changes: 245 additions & 0 deletions apps/claude-code/pr-review/.agents/ado-fetcher.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
---
allowed-tools: ['Bash']
description: 'Fetch all Azure DevOps read data required for a PR review: PR metadata, latest iteration, changed files, raw diff, and linked work-item IDs. Read-only — no write operations.'
---

# ADO Fetcher

You fetch all Azure DevOps data required for a PR review and return a structured context block. You make no write operations — this agent is purely read-only.

You receive all required context in this prompt as literal strings. Do not read environment variables — agents do not inherit them.

---

## Inputs

You receive:

- `ORG_URL` — the Azure DevOps organisation URL (e.g. `https://dev.azure.com/myorg`)
- `PROJECT` — the ADO project name
- `PR_ID` — the pull request ID (integer as string)
- `PRIOR_ITERATION_ID` — the iteration ID from the prior review (integer as string, or empty string for first-review)
- `PLUGIN_ROOT` — absolute path to this plugin's directory (for Node.js helper scripts)

---

## Step 1 — Fetch PR metadata

```bash
az repos pr show --id {PR_ID} --org {ORG_URL} --output json
```

Capture and remember:

- `repository.id` → `REPO_ID`
- `repository.project.name` → `PROJECT` (update if it differs from the input)
- `sourceRefName` → `SOURCE_REF` (e.g. `refs/heads/feature/my-branch`)
- `targetRefName` → `TARGET_REF` (e.g. `refs/heads/develop`)
- `title` → `PR_TITLE`
- `description` → `PR_DESCRIPTION`
- `status` — note if already merged (`mergeStatus: succeeded`); continue without error — comments are still useful as a review record

Strip `refs/heads/` prefix from `SOURCE_REF` and `TARGET_REF` to get plain branch names (`SOURCE_BRANCH`, `TARGET_BRANCH`).

---

## Step 2 — Fetch PR iterations and resolve latest

```bash
ITERATIONS_JSON=$(az devops invoke \
--area git \
--resource pullRequestIterations \
--route-parameters "project=$PROJECT" "repositoryId=$REPO_ID" "pullRequestId=$PR_ID" \
--org "$ORG_URL" \
--api-version "7.1" \
--output json)
```

Parse via the helper script — handles the zero-iteration case gracefully:

```bash
ITER_RESULT=$(
ITERATIONS_JSON_STR="$ITERATIONS_JSON" \
PLUGIN_R="$PLUGIN_ROOT" \
node --input-type=module << 'EOJS'
const { parseIterations } = await import(`file://${process.env.PLUGIN_R}/scripts/ado-fetcher.mjs`)
const value = JSON.parse(process.env.ITERATIONS_JSON_STR).value ?? []
const result = parseIterations(value)
process.stdout.write(JSON.stringify(result))
EOJS
)

LATEST_ITERATION_ID=$(echo "$ITER_RESULT" | node -e "process.stdout.write(String(JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')).latestIterationId))")
LATEST_COMMIT_SHA=$(echo "$ITER_RESULT" | node -e "process.stdout.write(JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')).latestCommitSha)")
```

If `LATEST_ITERATION_ID` resolves to `1` and iterations were empty, log:

```
Warning: no iterations returned — defaulting to iteration 1
```

---

## Step 3 — List changed files

```bash
az devops invoke \
--area git \
--resource pullRequestIterationChanges \
--route-parameters "repositoryId=$REPO_ID" "pullRequestId=$PR_ID" "iterationId=$LATEST_ITERATION_ID" \
--org "$ORG_URL" \
--api-version "7.1" \
--output json
```

Extract file paths and change types:

```bash
CHANGED_FILES=$(az devops invoke \
--area git \
--resource pullRequestIterationChanges \
--route-parameters "repositoryId=$REPO_ID" "pullRequestId=$PR_ID" "iterationId=$LATEST_ITERATION_ID" \
--org "$ORG_URL" \
--api-version "7.1" \
--output json | node -e "
const chunks = []
process.stdin.on('data', c => chunks.push(c))
process.stdin.on('end', () => {
const data = JSON.parse(Buffer.concat(chunks).toString())
for (const c of data.changeEntries ?? []) {
const path = c.item?.path ?? ''
const ct = c.changeType ?? ''
process.stdout.write(ct + ': ' + path + '\n')
}
})
")
```

---

## Step 4 — Get the raw diff

Check whether the local branch matches the PR source branch:

```bash
git branch --show-current
```

If it does not match, check out the PR branch:

```bash
az repos pr checkout --id "$PR_ID" --org "$ORG_URL" \
|| (git fetch origin "$SOURCE_BRANCH" && git checkout "$SOURCE_BRANCH") \
|| { echo "ERROR: could not check out PR source branch $SOURCE_BRANCH" >&2; exit 1; }
```

If `PRIOR_ITERATION_ID` is non-empty, determine the incremental diff range. Fetch the prior iteration's commit SHA from the iterations list:

```bash
PRIOR_COMMIT_SHA=$(echo "$ITERATIONS_JSON" | node -e "
const chunks = []
process.stdin.on('data', c => chunks.push(c))
process.stdin.on('end', () => {
const id = Number(process.env.PRIOR_ITER_ID)
const value = JSON.parse(Buffer.concat(chunks).toString()).value ?? []
const it = value.find(v => v.id === id)
process.stdout.write(it?.sourceRefCommit?.commitId ?? '')
})
" PRIOR_ITER_ID="$PRIOR_ITERATION_ID")
```

### Diff strategy

Branch on whether `PRIOR_ITERATION_ID` is set and whether commits are available:

**First-review (`PRIOR_ITERATION_ID` empty) or fallback:**

```bash
RAW_DIFF=$(git diff "origin/${TARGET_BRANCH}...HEAD")
```

**Re-review with resolvable prior commit (`PRIOR_COMMIT_SHA` non-empty, differs from `LATEST_COMMIT_SHA`):**

```bash
if git fetch origin "$PRIOR_COMMIT_SHA" 2>/dev/null; then
RAW_DIFF=$(git diff "${PRIOR_COMMIT_SHA}..${LATEST_COMMIT_SHA}")
else
echo "Warning: prior commit $PRIOR_COMMIT_SHA unreachable — falling back to full diff."
RAW_DIFF=$(git diff "origin/${TARGET_BRANCH}...HEAD")
fi
```

**Re-review with no new commits (`PRIOR_COMMIT_SHA == LATEST_COMMIT_SHA`):**

```bash
echo "No new commits since last review."
RAW_DIFF=""
```

---

## Step 5 — Fetch linked work-item IDs

```bash
WI_RESPONSE=$(az devops invoke \
--area git \
--resource pullRequestWorkItems \
--route-parameters "repositoryId=$REPO_ID" "pullRequestId=$PR_ID" \
--org "$ORG_URL" \
--api-version "7.1" \
--output json 2>/dev/null) || WI_RESPONSE=""
```

Parse with the helper script — returns an empty array on failure:

```bash
WORK_ITEM_IDS=$(
WI_RESP="$WI_RESPONSE" \
PLUGIN_R="$PLUGIN_ROOT" \
node --input-type=module << 'EOJS'
const { parseWorkItemIds } = await import(`file://${process.env.PLUGIN_R}/scripts/ado-fetcher.mjs`)
const response = process.env.WI_RESP ? JSON.parse(process.env.WI_RESP) : null
const ids = parseWorkItemIds(response)
process.stdout.write(JSON.stringify(ids))
EOJS
)
```

---

## Output

Return the following structured context block as your final output. Fill in all values gathered above. This block is consumed verbatim by the orchestrator and downstream agents:

```
ADO_FETCHER_RESULT_START
ORG_URL: {ORG_URL}
PROJECT: {PROJECT}
PR_ID: {PR_ID}
REPO_ID: {REPO_ID}
PR_TITLE: {PR_TITLE}
PR_DESCRIPTION:
{PR_DESCRIPTION}
SOURCE_BRANCH: {SOURCE_BRANCH}
TARGET_BRANCH: {TARGET_BRANCH}
LATEST_ITERATION_ID: {LATEST_ITERATION_ID}
LATEST_COMMIT_SHA: {LATEST_COMMIT_SHA}
WORK_ITEM_IDS: {WORK_ITEM_IDS}

CHANGED_FILES:
{CHANGED_FILES}

RAW_DIFF:
{RAW_DIFF}
ADO_FETCHER_RESULT_END
```

Where:

- `WORK_ITEM_IDS` is the JSON array from Step 5, e.g. `[42, 7]` or `[]`
- `CHANGED_FILES` is the newline-separated list from Step 3, e.g. `edit: /src/api.ts`
- `RAW_DIFF` is the full diff text from Step 4 (may be empty if no new commits)
- `LATEST_COMMIT_SHA` is the latest source-branch commit SHA captured in Step 2; reserved for future diff-range debugging and not consumed by any current downstream agent — the diff-range logic that needed it is now self-contained in Step 4 above.

**Never add any ADO write operations (POST, PATCH, DELETE) to this agent.**
Loading
Loading