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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **Experimental:** repository context for reviews, off by default (`reviewRepoContext: true` to turn it on). It may change or be removed before it is released; whether it improves reviews has not been measured on live pull requests yet. Each batch also gets a map of the repository's files and numbered excerpts of up to 12 related files: files that use a function, method, class or type the batch changes, and files the changed files import (TypeScript/JavaScript, Python, Go, PowerShell and Bicep). It is read from the pipeline's existing checkout with `git`: no clone, no Azure DevOps API call, no new permission. The budget, `reviewContextMaxTokens` (default 12000), is taken out of `reviewMaxBatchTokens`. The context is fenced as untrusted like the diff, the base prompt has a new "Using Repository Context" section, and comments still only go on the diff. The metrics event adds `contextFiles` and `contextTokens`, and the run summary line reports them. `tools/Measure-RepoContext.ps1` estimates the extra prompt tokens for your own pull requests without calling a model.
- README: badges, a feature summary, a diagram of how a run works, a documentation index, and a copy-paste prompt that lets an AI coding agent set up the review stage and, optionally, the metrics dashboard step by step.

### Changed
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Please report security problems privately as described in [SECURITY.md](SECURITY
| `ai-code-review/` | The review engine and the stage template | PowerShell 7; Pester, PSScriptAnalyzer |
| `metrics/` | The optional metrics API and dashboard | Go; gofmt, go vet, go test, Docker build |
| `infra/` | Bicep and Entra scripts for the metrics service | Bicep build and lint, shellcheck |
| `tools/` | Maintenance scripts, such as the parameter reference generator | PowerShell 7; PSScriptAnalyzer |
| `tools/` | Maintenance scripts, such as the parameter reference generator and `Measure-RepoContext.ps1` | PowerShell 7; PSScriptAnalyzer |
| `docs/` | Guides linked from the README | |
| `examples/` | Example pipeline and rules files | |

Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ PullPicket is a pipeline stage template that sends the changes in a pull request
- Uses a shared base prompt plus a short rules file that you keep in your own repository, so each repository describes its own stack and conventions.
- Splits large pull requests into batches, skips lockfiles, generated output and binaries, and stays within per-file and per-batch token limits.
- Remembers its own earlier comments on the pull request, so a new push does not repeat a finding that is already open.
- Experimental, off by default: reads related code from the rest of the repository (a file map, the callers of what changed, the files it imports) so the review can catch a change that breaks another file. Read-only, from the pipeline's own checkout, within a token budget.
- Treats the diff as untrusted data: text in the code that tries to steer the reviewer is flagged instead of followed.
- Signs in to the model and to Azure DevOps with the identity of an Azure Resource Manager service connection (Entra ID). No API keys are needed by default.
- Optionally sends one event per run (tokens, comments, outcome, latency) to a small metrics API with a dashboard.
Expand Down Expand Up @@ -313,6 +314,7 @@ The stage takes its settings as template parameters. The ones you are most likel
| `metricsAuth` | `entra` | `entra` sends a token for `aiMetricsApiResource` from the service connection; `key` sends `aiMetricsApiKey` as `X-Api-Key`. |
| `reviewPathSpec` / `reviewExcludePathSpec` | empty | Limit the review to, or exclude, paths. |
| `reviewDefaultExcludes` | `true` | Skip lockfiles, minified files, images, fonts, archives and binaries. |
| `reviewRepoContext` | `false` | Experimental. Send related code from the rest of the repository with each batch (up to `reviewContextMaxTokens`, default 12000). |

Every parameter, with its type, default and allowed values, is in the [parameter reference](docs/parameters.md). It is generated from the comments in [`ai-code-review/stage-template.yml`](ai-code-review/stage-template.yml), and CI fails if the two drift apart.

Expand All @@ -325,6 +327,7 @@ You pay your Azure model provider per token; PullPicket adds no charge. What dri
- **Diff size.** Each run sends the line-numbered diff of the pull request plus the base prompt and rules file. Bigger pull requests cost more.
- **Batches.** A large pull request is split into batches (`reviewMaxFilesPerBatch`, `reviewMaxBatchTokens`), and each batch repeats the prompt. Very large single files are truncated at `reviewMaxFileTokens`.
- **Re-runs.** Every push to the pull request runs a new full review of the whole diff.
- **Repository context.** With `reviewRepoContext: true`, each batch also carries up to `reviewContextMaxTokens` of related code. The budget is taken out of `reviewMaxBatchTokens`, so no single request grows, but the run as a whole sends more prompt tokens: about 10% more on a median pull request in a small repository and about 75% more in a 2,700-file TypeScript repository, where small pull requests fill most of the budget. Output tokens do not change. Run `pwsh tools/Measure-RepoContext.ps1 -TargetRef <base> -SourceRef <branch>` in your own clone to see the estimate for your pull requests before you turn it on, and lower `reviewContextMaxTokens` to cap it.
- **Reasoning.** Reasoning models also bill reasoning tokens; `reasoningEffort` controls how many.

Ways to keep costs down: leave `reviewDefaultExcludes` on, use `reviewPathSpec` or `reviewExcludePathSpec` to skip generated or vendored code, pick a smaller model for routine repositories, and use the metrics dashboard to see tokens and estimated cost per run.
Expand Down Expand Up @@ -358,7 +361,7 @@ To connect a pipeline, create a variable group with `aiMetricsApiUrl` and `aiMet
| `metrics/` | Optional metrics ingestion API and dashboard (Go, Cosmos DB). |
| `infra/` | Bicep and Entra setup scripts for the metrics infrastructure. |
| `docs/` | Parameter reference, troubleshooting and the metrics guide. |
| `tools/` | Maintenance scripts, such as the parameter reference generator. |
| `tools/` | Maintenance scripts, such as the parameter reference generator and the repository context cost estimate. |
| `.github/` | GitHub Actions CI and Dependabot configuration. |
| `.azuredevops/` | Azure Pipelines CI for the engine, for teams that host a copy in Azure Repos. |

Expand Down
6 changes: 4 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ Each review run sends one or more chat completion requests to the model deployme
| Prior review rounds | Only when the pull request already has PullPicket comments: for each, the file path, line number, severity, thread status, and the first 150 characters of the first line of the comment. |
| The diff | A legend naming the source and target branches; for each reviewed file, its path (and the old path if it was renamed) and its changed lines with 5 lines of context around each change, numbered. Lines past `reviewMaxFileTokens` are cut. |
| Deleted files | Only with `reviewDeletedFiles: true`: the paths of deleted files, not their content. |
| Repository context | Only with `reviewRepoContext: true`: a map of the folders and file names on the pull request branch (top folders with file counts in a large repository, plus the file names in the changed files' folders), and numbered excerpts of up to 12 related files per batch (files that use a function or type the batch changes, and files the changed files import), up to `reviewContextMaxTokens` per batch. Excluded paths are left out. It is read from the pipeline's own checkout with `git`, so it needs no extra access. |
| Settings | The `modelName` and, if set, `reasoningEffort`, plus the JSON schema the answer must match. |

The model does **not** receive: whole files beyond the context lines, files that are excluded (`reviewDefaultExcludes`, `reviewExcludePathSpec`, or not matched by `reviewPathSpec`), the pull request title or description, commit messages, other people's comments on the pull request, pipeline variables, or any credential.
The model does **not** receive: whole files beyond the context lines (except the related-file excerpts above, when `reviewRepoContext` is on), files that are excluded (`reviewDefaultExcludes`, `reviewExcludePathSpec`, or not matched by `reviewPathSpec`), the pull request title or description, commit messages, other people's comments on the pull request, pipeline variables, or any credential.

A large pull request is split into batches, and each batch is its own request that repeats the system message and prior review rounds.

Expand All @@ -50,6 +51,7 @@ Where the data goes after that depends on your model deployment: its region, dat
The diff is written by the pull request author, so PullPicket treats it as hostile input:

- The diff is wrapped between marker lines that carry a random id generated for each request. The author cannot know the id, so text in the diff cannot forge the closing marker and "escape" into instructions.
- Repository context (`reviewRepoContext: true`) comes from the pull request branch too, so the author can change any file in it. It is wrapped in its own markers with their own random id and gets the same untrusted-content rules. The model is told never to comment on context files, and a comment on a file outside the diff is dropped before posting.
- The base prompt tells the model that everything inside the diff and the prior review rounds is data under review, never instructions. Text that tries to steer the reviewer ("ignore previous instructions", "this file is pre-approved", "return an empty review") is not followed; the model is told to flag it as a `WARNING` on the nearest changed line instead.
- The answer must match a strict JSON schema (file name, line number, severity, comment). Comments that point at a file or line outside the diff are moved to the nearest changed line or dropped.
- The reviewer can only comment. It has no tools, cannot run code, cannot vote, approve or complete a pull request, and cannot change the repository.
Expand All @@ -66,7 +68,7 @@ Only when you set `metricsVariableGroup`, each run sends one JSON event to your

- `featureName` (`ai-code-review`), `source` (`ado-pipeline`), and the server-set `eventId`, `timestamp` and `receivedAt`;
- the Azure DevOps **project name**, **repository name**, pull request id, pipeline run id, and the **source and target branch names**;
- counts: files changed, reviewed, excluded, renamed, deleted and truncated; lines added and removed; batches; comments proposed, posted, skipped as duplicates, moved and dropped; and comments per severity;
- counts: files changed, reviewed, excluded, renamed, deleted and truncated; lines added and removed; batches; comments proposed, posted, skipped as duplicates, moved and dropped; comments per severity; and, with `reviewRepoContext`, the number of related files and estimated tokens of repository context sent;
- the **file paths that received comments**;
- the run's outcome, error type and HTTP status code if it failed, latency, token counts, the model name and the PullPicket version.

Expand Down
1 change: 1 addition & 0 deletions ai-code-review/AICodeReview.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
foreach ($file in @(
'Resolve-AdoCollectionUrl.ps1',
'Get-CodeChanges.ps1',
'Get-RepoContext.ps1',
'Get-ExistingReviewThreads.ps1',
'Invoke-LLMCodeReview.ps1',
'Set-PullRequestComments.ps1'
Expand Down
5 changes: 5 additions & 0 deletions ai-code-review/Get-CodeChanges.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,10 @@ function New-FileDiffBlock {
Add-ParsedDiffToBuilder -Builder $builder -FullPath $fullPath -DiffLines $lines -TotalAdded $addedRef -TotalRemoved $removedRef -Notice $notice -Anchors $anchors -LineText $lineText
$text = $builder.ToString()

# Git names the enclosing function after a hunk's second @@ ("@@ -1,3 +1,4 @@ function save(").
# Repository context uses it to find callers of a function whose body changed.
$hunkFunctions = @($lines | ForEach-Object { if ($_ -match '^@@ [^@]* @@ (.+)$') { $matches[1].Trim() } } | Where-Object { $_ } | Select-Object -Unique)

return [PSCustomObject]@{
File = $File
FullPath = $fullPath
Expand All @@ -362,6 +366,7 @@ function New-FileDiffBlock {
Tokens = Get-EstimatedTokenCount -Text $text
Truncated = ($omittedLines -gt 0)
OmittedLines = $omittedLines
HunkFunctions = $hunkFunctions
}
}

Expand Down
Loading
Loading