Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
60d1550
feat: optional Forge.list_paths with PathListing, replay delegation a…
sblattj Sep 24, 2026
b106b2d
test: add the issue #17 repository-context acceptance fixture (T16)
sblattj Sep 24, 2026
f78610d
feat: add RepoDir local filesystem reader for --repo-dir (T11)
sblattj Sep 24, 2026
2685c14
feat: add the four 0.16.0 repo-context config keys (#17 seat T1)
sblattj Sep 24, 2026
2aa5b0f
merge: seat/T8 into release/0.16.0 (0.16.0 wave 1, seat T8)
sblattj Sep 24, 2026
d986ace
feat: repo_context definitions core with Java type declarations (#17 T2)
sblattj Sep 24, 2026
69b82fa
merge: seat/T11 into release/0.16.0 (0.16.0 wave 1, seat T11)
sblattj Sep 24, 2026
9464be7
merge: seat/T2 into release/0.16.0 (0.16.0 wave 1, seat T2)
sblattj Sep 24, 2026
651b57b
merge: seat/T1 into release/0.16.0 (0.16.0 wave 1, seat T1)
sblattj Sep 24, 2026
2669ecd
merge: seat/T16 into release/0.16.0 (0.16.0 wave 1, seat T16)
sblattj Sep 24, 2026
b522b9e
feat: list_paths for GitLab and Bitbucket Server / Data Center (#17 T9)
sblattj Sep 24, 2026
cd792cb
feat: list_paths for Bitbucket Cloud and Azure DevOps (#17, T10)
sblattj Sep 24, 2026
6eb22b6
feat: contract excerpters for repository context (#17, T4)
sblattj Sep 24, 2026
6cf038d
merge: seat/T9 into release/0.16.0 (0.16.0 wave 2, seat T9)
sblattj Sep 24, 2026
655c50c
merge: seat/T10 into release/0.16.0 (0.16.0 wave 2, seat T10)
sblattj Sep 24, 2026
d251e69
feat: run-scoped repository reader with single-flight reads and caps …
sblattj Sep 24, 2026
f8d50ed
feat: cross-chunk and diff-file definitions for repository context (#…
sblattj Sep 24, 2026
230cab3
merge: seat/T4 into release/0.16.0 (0.16.0 wave 1, seat T4)
sblattj Sep 24, 2026
d4f506d
merge: seat/T12 into release/0.16.0 (0.16.0 wave 2, seat T12)
sblattj Sep 24, 2026
a8cf9ed
feat: repository-context resolver (candidate files per referenced name)
sblattj Sep 24, 2026
4cfa8e3
test: exercise the (path, line) dedup in the cross-chunk nested-type …
sblattj Sep 24, 2026
6453811
merge: seat/T5 into release/0.16.0 (0.16.0 wave 2, seat T5)
sblattj Sep 24, 2026
022173b
merge: seat/T3 into release/0.16.0 (0.16.0 wave 2, seat T3)
sblattj Sep 24, 2026
cd09953
feat: add repo_dir eval case field (issue #17, T15a)
sblattj Sep 24, 2026
099004c
merge: seat/T15a into release/0.16.0 (0.16.0 wave 3, seat T15a)
sblattj Sep 24, 2026
f7919b5
feat: contract triggers, contract-file selection and per-chunk contra…
sblattj Sep 24, 2026
741508e
merge: seat/T6 into release/0.16.0 (0.16.0 wave 3, seat T6)
sblattj Sep 24, 2026
57390d6
feat: per-chunk repository context unit (sources, order, budget, excl…
sblattj Sep 25, 2026
c139a5f
merge: seat/T7 into release/0.16.0 (0.16.0 wave 4, seat T7)
sblattj Sep 25, 2026
311a548
feat: wire repository context into orchestrate_review
sblattj Sep 25, 2026
5d8cd80
fix: GitLab list_paths walks GraphQL tree.blobs before the REST tree
sblattj Sep 25, 2026
fa39b24
merge: seat/T13 into release/0.16.0 (0.16.0 wave 5, seat T13)
sblattj Sep 25, 2026
c30d620
merge: seat/T19 into release/0.16.0 (0.16.0 wave 5, seat T19)
sblattj Sep 25, 2026
3e467ce
fix: retry an empty model reply once in the reviewer
sblattj Sep 25, 2026
303184b
feat: wire repository context through the CLI and prxref eval (#17)
sblattj Sep 25, 2026
853fee2
test: issue #17 acceptance through the real orchestrator (T17)
sblattj Sep 25, 2026
729dbcb
merge: seat/T14 into release/0.16.0 (0.16.0 wave 6, seat T14)
sblattj Sep 25, 2026
ddeacf4
merge: seat/T20 into release/0.16.0 (0.16.0 wave 6, seat T20)
sblattj Sep 25, 2026
7b10cb2
merge: seat/T17 into release/0.16.0 (0.16.0 wave 6, seat T17)
sblattj Sep 25, 2026
ec48744
docs: repository context (#17) user docs and 0.16.0 changelog
sblattj Sep 25, 2026
9e17b1e
merge: seat/T18 into release/0.16.0 (0.16.0 wave 7, seat T18)
sblattj Sep 25, 2026
2b1472a
release: 0.16.0 — repository context
sblattj Sep 25, 2026
bdb37b4
merge: seat/REL into release/0.16.0 (0.16.0 wave 8, seat REL)
sblattj Sep 25, 2026
229bdad
chore: drop build-internal IDs from comments and docstrings
sblattj Sep 25, 2026
236ce08
merge: seat/IDS into release/0.16.0 (0.16.0 wave 9, seat IDS)
sblattj Sep 25, 2026
507a405
docs: list every trace event and phase, including 0.16's context events
sblattj Sep 25, 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
33 changes: 33 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,39 @@ PRXREF_MAX_CHUNKS=8
# a visible marker. Must be > 0.
# PRXREF_TICKET_CONTEXT_MAX_CHARS=6000

# Repository context (0.16.0): off (default) | diff | repo. off leaves every
# prompt, post, trace and default-verbosity log byte-identical to 0.15.0.
# diff adds cross-chunk definitions from other files already in the diff,
# plus diff-file entries, all read from the diff itself -- no repository
# reader is needed. repo also reads files outside the diff (import,
# path-convention and name-search definitions, plus contract excerpts)
# through the forge's repository reader when one is available, or
# --repo-dir. Matching is exact and case-sensitive, like PRXREF_FAIL_ON; any
# other value is a configuration error (exit 2).
# PRXREF_REPO_CONTEXT=off

# Repository context (0.16.0): per-chunk character budget shared by the
# cross-chunk, contract and other repository-context entries. Must be > 0.
# PRXREF_REPO_CONTEXT_MAX_CHARS=12000

# Repository context (0.16.0): globs (matched like PRXREF_SIZE_IGNORE_GLOBS)
# selecting the contract files -- OpenAPI, JSON Schema, Liquibase/SQL
# migrations -- excerpted under "repo". A set value REPLACES the built-in
# set below rather than adding to it; an empty value reads as unset, so the
# built-in set stays -- there is no way to turn contract excerpts off on
# their own in 0.16.0 short of setting PRXREF_REPO_CONTEXT to off or diff.
# Built-in set: **/openapi*.y*ml, **/openapi*.json, **/openapi/**,
# **/swagger*, **/*.schema.json, **/db/changelog/**, **/db/migration/**,
# **/migrations/**
# PRXREF_CONTEXT_CONTRACT_GLOBS=

# Repository context (0.16.0): globs (matched like PRXREF_SIZE_IGNORE_GLOBS)
# whose paths are never read for repository context, not even a diff file.
# ADDED to a floor that is always on: **/expected.json, **/cases.json,
# **/case.json, **/prxref-eval/**, **/.env*, **/*.pem, **/*.key. Empty
# (the default) adds nothing.
# PRXREF_CONTEXT_EXCLUDE_GLOBS=

# Jira base URL (scheme://host plus any context path) that ticket fetches are
# looked up on, overriding a ticket URL's own base (a self-hosted board often
# sits behind a different REST host than its browse URL). Jira credentials
Expand Down
161 changes: 160 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,163 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
Issue numbers in entries before 0.14.0 refer to the project's previous issue
tracker.

## [0.16.0] — 2026-09-25

The repository-context release (#17). A chunk worker can now see code outside
its own hunks: definitions from the pull request's other files and from files
the diff never touches, and excerpts of the API and database contracts a
change points at. It is off by default. `PRXREF_REPO_CONTEXT=diff` adds
definitions found in the pull request's own files, and `repo` also reads the
rest of the repository, through the forge or through a local checkout given
with `--repo-dir`. At `off`, the prompts, posted comments, trace and logs are
byte-identical to 0.15.0; the run record and `--format json` gain one key,
`repo_context`, which is `null`.

### Added

- **Repository context (#17).** `PRXREF_REPO_CONTEXT` sets how much of the
repository a chunk worker sees beyond its own hunks: `off` (the default),
`diff` or `repo`. `diff` shows the definitions that a chunk's added lines
reference from the pull request's other files and, for a type another chunk
changes, the changed lines inside it. Those files are read at the PR head
through the forge or `--repo-dir`; with no reader, the entries come from the
diff's hunk lines alone. `repo` also reads files outside the diff:
definitions found through a file's imports, through Java's same-package
path convention and by a search of the repository's file names, plus
contract excerpts. Definitions extend the prompt's `### Definitions
referenced by this chunk` block, and contract excerpts form a new
`### Contract excerpts` block after it. Only chunk workers get repository
context: the whole-PR sweep prompt is unchanged. At `repo`, a run with no
repository reader, or with no file listing, logs one WARNING saying what it
runs without. Matching is exact and case-sensitive, and any other value
exits 2 naming the variable.
- **Java definitions (#17).** 0.15.0 showed a chunk the definitions it
references for JavaScript, TypeScript and Python only. At `diff` and `repo`,
Java type declarations (`class`, `interface`, `record`, `enum` and
`@interface`, never a method or a field) are found too, in the chunk's own
Java files outside its hunks as well as in other files. At `off`, Java still
gets none.
- **`PRXREF_REPO_CONTEXT_MAX_CHARS` (#17).** The repository-context entries of
one chunk share a character budget, default `12000`, which must be greater
than 0. An entry costs the length of its `path:line: text` line. Entries are
admitted in the rank order of their `reason`: `cross-chunk`, `contract`,
`diff-file`, `import`, `path-convention`, then `name-search`, and within a
rank by path and line. Admission stops at the first entry that does not
fit, and a `… N more context entries omitted` line, not counted against the
budget, ends the block of the first entry left out.
- **Contract excerpts and `PRXREF_CONTEXT_CONTRACT_GLOBS` (#17).** At `repo`, a
chunk whose added lines use a route, an operation id, a table, or a name
that matches a schema gets the matching slice of the repository's contract
files: an OpenAPI operation or schema, a JSON Schema, or an earlier
migration of a changed one. `PRXREF_CONTEXT_CONTRACT_GLOBS` picks the
contract files once per run; its built-in set is `**/openapi*.y*ml`,
`**/openapi*.json`, `**/openapi/**`, `**/swagger*`, `**/*.schema.json`,
`**/db/changelog/**`, `**/db/migration/**` and `**/migrations/**`. A set
value replaces the built-in set instead of adding to it, and an empty value
keeps it. A chunk reads at most 6 spec files (`.yaml`, `.yml` or `.json`)
and, for each changed migration, at most the 4 nearest earlier migrations in
its directory; each excerpt is capped at 40 lines and 2,000 characters.
- **`PRXREF_CONTEXT_EXCLUDE_GLOBS` and an exclude floor (#17).** Repository
context never lists, reads or shows a path under a floor that is always on:
`**/expected.json`, `**/cases.json`, `**/case.json`, `**/prxref-eval/**`,
`**/.env*`, `**/*.pem` and `**/*.key`, which keeps eval labels, eval output,
dotenv files and keys out of the prompt. `PRXREF_CONTEXT_EXCLUDE_GLOBS` adds
globs to that floor; a `!` negation in it never re-admits a floor path.
Neither list hides a changed file's hunks, which still reach the prompt as
the diff.
- **Bounded repository reads (#17).** One reader serves the whole run: each
path is fetched at most once, and the file listing once. Reads of paths
outside the pull request's own files are capped at 16 per chunk and 200 per
run; a read past a cap is skipped, so the chunk gets fewer entries, and the
run record's `read_cap_hit` says so. The pull request's own files are read
without a cap, so the entries built from them never depend on which chunk
read a file first.
- **`--repo-dir PATH` (#17).** `prxref review --repo-dir PATH` names a local
checkout of the repository at the PR head. With `PRXREF_REPO_CONTEXT=repo`,
repository context reads and lists files there instead of calling the forge
(at `diff` it only reads there), so a `--diff-file` review gets repository
context with no network. A path that is not an existing directory exits 2
before any network call. It is not a replay flag: on its own it neither
stops posting nor adds a `replay` stamp.
- **Repository context in `prxref eval` (#17).** A `cases.json` case takes an
optional `repo_dir`, read relative to the file, and a `case-*/` directory
takes a `repo/` directory; either is the case's `--repo-dir`. A `repo_dir`
that is not an existing directory exits 2 naming it. `run.json` records the
four new settings, so two runs that differ only in `PRXREF_REPO_CONTEXT` can
be told apart and compared.
- **`repo_context` in the run record (#17).** The run record and
`--format json` gain `repo_context`, always present and `null` at `off`. On,
it holds the level, the budget, both glob lists, the reader (`forge`,
`repo-dir` or `null`), the listing's path count and whether it is complete,
the read count, `read_cap_hit`, and one row per chunk naming each admitted
entry's path, line, symbol, kind, reason and length, how many entries were
left out, and whether the timeout retry dropped them. It never holds file
text. The JSONL trace gains one `chunk context` event per chunk and one
`repo_context ok` event per run, and in text output `-v` prints a
`repo context:` line with the level, the reader, the listing size, the read
count, whether a cap was hit, and the entries admitted and left out. With
the feature off none of them appears.
- **Repository listing on every forge (#17).** Each forge adapter can now list
the repository's files at a commit, which `repo` does once per run. GitHub
reads `GET /repos/{owner}/{repo}/git/trees/{sha}?recursive=1` in one request.
GitLab walks GraphQL's `project.repository.tree(recursive: true).blobs`, 100
files a page, and falls back to the REST `repository/tree?recursive=true`
walk when the first GraphQL page is unusable. Bitbucket Cloud walks
`/2.0/repositories/{owner}/{repo}/src/{sha}/?max_depth=64&pagelen=100`,
Bitbucket Server walks `/rest/api/1.0/projects/{key}/repos/{slug}/files` at
the commit, 100 paths a page, and Azure DevOps reads the `items` endpoint
with `recursionLevel=Full` at the commit in one request. A paged walk stops
at 20 pages. Only files are listed: directories and submodules are dropped,
though whether Bitbucket Server returns submodules is unverified. A listing
that stopped early is marked incomplete (`complete: false` in the run
record, `(partial)` on the `-v` line), a listing whose first request fails
is none at all, and neither fails a review. See `docs/forges.md`.

### Fixed

- **An empty model reply is now asked for once more.** A reply with no text, or
only whitespace, is sent again once with the same prompt and the same
budget, after one WARNING (`<unit>: empty model reply
(finish_reason=<reason>); retrying once`). This covers chunks and the
systemic sweep. A reply the provider stopped at the budget (`finish_reason`
`length` or `max_tokens`) is not retried, because it already names
`PRXREF_LLM_MAX_TOKENS`. Neither is a non-empty reply that fails to parse,
nor a call that raised. Token counts and elapsed time cover both calls, and
the reported cost is their sum, or unknown when either call reported none.
In 0.15.0 and earlier the unit failed with `no parseable content` even
though the provider billed it; the review of 0.15.0's own pull request lost
1 of 8 chunks this way.

### Known limitations

- **Read caps cut the lowest-ranked sources first.** A chunk issues its capped
reads in rank order, contract files before the files that imports, the path
convention and the name search point at, so those definitions are the first
left out once a chunk has made 16 reads or the run 200, and which chunk
meets the run cap first depends on thread scheduling.
- **The name search matches file names, not file contents.** Outside the pull
request, a definition is found only through an import, the Java path
convention, or a same-language file whose name, less its extension, matches
the referenced name, so a type declared in a differently named file is not
shown.
- **A file over 512 KiB is read as missing.** Every forge and `--repo-dir`
refuse a file past that size, so a large OpenAPI spec gives no excerpt.
- **A large repository is listed only in part.** A paged listing stops at 20
pages and GitHub truncates the tree of a very large repository, so there the
name search and the contract globs see only the files listed; on a large
GitLab project the listing can take about a minute, once per run, at `repo`
only.
- **The Bitbucket Server listing is untested live.** It is built from the
Bitbucket Data Center REST documentation, tested against fakes of that
shape, and has not been run against a live instance.
- **A chunk that times out loses its repository context.** The timeout retry
drops the definitions and contract blocks to shrink the prompt, so with a
model slower than `PRXREF_LLM_TIMEOUT` (default `45` seconds) a chunk is
reviewed without them; raise the timeout for a slow model.
- **A pull request's file can be fetched twice.** The dependency and same-file
definition blocks of 0.15.0 keep their own reader, so a file both they and
repository context read costs two requests per run.

## [0.15.0] — 2026-09-24

The tuning release. A team can now replace the review prompts, scope its review
Expand Down Expand Up @@ -1580,7 +1737,9 @@ Development baseline. Never published to PyPI and never tagged; superseded by
- Diff content is sent to whichever OpenAI-compatible endpoint you configure.
- Requires Python 3.12+. Tested on 3.12 and 3.13.

[Unreleased]: https://github.com/sblattj/prxref/compare/v0.14.0...HEAD
[Unreleased]: https://github.com/sblattj/prxref/compare/v0.16.0...HEAD
[0.16.0]: https://github.com/sblattj/prxref/releases/tag/v0.16.0
[0.15.0]: https://github.com/sblattj/prxref/releases/tag/v0.15.0
[0.14.0]: https://github.com/sblattj/prxref/releases/tag/v0.14.0
[0.13.0]: https://github.com/sblattj/prxref/releases/tag/v0.13.0
[0.12.2]: https://github.com/sblattj/prxref/releases/tag/v0.12.2
Expand Down
Loading
Loading