Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
8987cce
feat: add PRXREF_LLM_PARSE_RETRIES config key
sblattj Sep 25, 2026
5512284
merge: seat/U1 into release/0.17.0 (0.17.0 wave 1, seat U1)
sblattj Sep 25, 2026
0b8437d
feat: flag a default-on toggle the test setup pins off (#22)
sblattj Sep 25, 2026
a466e5c
feat: retry a worker or sweep reply that cannot be used (#21)
sblattj Sep 25, 2026
6186ece
feat: parse Gradle build files and the version catalog for JVM depend…
sblattj Sep 25, 2026
9a328b4
feat: safe pom.xml reader for JVM dependency context (#20)
sblattj Sep 25, 2026
f6c3311
merge: main (v0.16.0 release, 3025628) into release/0.17.0
sblattj Sep 25, 2026
f4f0185
merge: seat/U17 into release/0.17.0 (0.17.0 wave 1, seat U17)
sblattj Sep 25, 2026
3b7b06f
merge: seat/U5 into release/0.17.0 (0.17.0 wave 1, seat U5)
sblattj Sep 25, 2026
f4b93b2
feat: jvm_lang, Java and Kotlin definition regexes, keywords and impo…
sblattj Sep 25, 2026
a47b69e
merge: seat/U4 into release/0.17.0 (0.17.0 wave 1, seat U4)
sblattj Sep 25, 2026
014339c
merge: seat/U3 into release/0.17.0 (0.17.0 wave 1, seat U3)
sblattj Sep 25, 2026
01d1391
feat: find readers of the shared state a chunk writes (#22)
sblattj Sep 25, 2026
4b65f93
merge: seat/U2 into release/0.17.0 (0.17.0 wave 1, seat U2)
sblattj Sep 25, 2026
4e1849c
merge: seat/U16 into release/0.17.0 (0.17.0 wave 1, seat U16)
sblattj Sep 25, 2026
b9d0bf2
feat: retry a judge reply the parser rejects (#21)
sblattj Sep 25, 2026
ab83dd4
merge: seat/U10 into release/0.17.0 (0.17.0 wave 2, seat U10)
sblattj Sep 25, 2026
268a99c
feat: JVM dependency lines for a changed Java or Kotlin file (#20)
sblattj Sep 25, 2026
062ea2c
feat: add readers of shared state to repository context at repo (#22)
sblattj Sep 25, 2026
0df600a
feat: thread the parse retry through the orchestrator and CLI, and re…
sblattj Sep 25, 2026
7483492
merge: seat/U6 into release/0.17.0 (0.17.0 wave 2, seat U6)
sblattj Sep 25, 2026
cfc023e
merge: seat/U18 into release/0.17.0 (0.17.0 wave 2, seat U18)
sblattj Sep 25, 2026
b5d0c73
merge: seat/U9 into release/0.17.0 (0.17.0 wave 2, seat U9)
sblattj Sep 25, 2026
1ae1335
feat: repo context shares jvm_lang's Java facts and handles Kotlin ty…
sblattj Sep 25, 2026
1cb7404
feat: record the parse retry in evals (#21)
sblattj Sep 25, 2026
f51effd
test: make the Kotlin import-alias test able to fail (#20)
sblattj Sep 25, 2026
6d9afce
feat: wire the pinned-off toggle check into the review (#22)
sblattj Sep 25, 2026
e40f990
merge: seat/U8 into release/0.17.0 (0.17.0 wave 3, seat U8)
sblattj Sep 25, 2026
70512fe
test: accept the parse retry end to end through the CLI path (#21)
sblattj Sep 25, 2026
d75990a
merge: seat/U11 into release/0.17.0 (0.17.0 wave 3, seat U11)
sblattj Sep 25, 2026
e09ef45
merge: seat/U19 into release/0.17.0 (0.17.0 wave 3, seat U19)
sblattj Sep 25, 2026
48ca5de
merge: seat/U14 into release/0.17.0 (0.17.0 wave 3, seat U14)
sblattj Sep 25, 2026
a7d79e1
feat: Java and Kotlin in chunk context: definitions, annotations, dep…
sblattj Sep 25, 2026
fc04681
Merge branch 'release/0.17.0' into seat/U7
sblattj Sep 25, 2026
136b8db
test: issue #22 acceptance over the issue's own fixture, end to end
sblattj Sep 25, 2026
2304682
fix: search a chunk's own files for the names only its other files re…
sblattj Sep 25, 2026
50ae5fb
merge: seat/U20 into release/0.17.0 (0.17.0 wave 4, seat U20)
sblattj Sep 25, 2026
362dca9
merge: seat/U7 into release/0.17.0 (0.17.0 wave 3, seat U7)
sblattj Sep 25, 2026
e82f1c8
test: compare the #17 golden reads without JVM paths and pin the JVM …
sblattj Sep 25, 2026
3cfec8e
merge: seat/U12 into release/0.17.0 (0.17.0 wave 5, seat U12)
sblattj Sep 25, 2026
667e1f9
docs: CHANGELOG and docs for 0.17.0 (#20, #21, #22 parts 2 and 3)
sblattj Sep 25, 2026
8264889
test: issue #20 acceptance for JVM chunk context, end to end against …
sblattj Sep 25, 2026
8fc9704
merge: seat/U15 into release/0.17.0 (0.17.0 wave 5, seat U15)
sblattj Sep 25, 2026
c689328
merge: seat/U13 into release/0.17.0 (0.17.0 wave 5, seat U13)
sblattj Sep 25, 2026
4553cef
release: 0.17.0 version bump, CHANGELOG date and handoff
sblattj Sep 25, 2026
1b73e1b
merge: seat/REL into release/0.17.0 (0.17.0 wave 6, seat REL)
sblattj Sep 25, 2026
099a7ce
fix: a deterministic check's finding keeps its own severity
sblattj Sep 25, 2026
c8c3675
merge: seat/FIX into release/0.17.0 (0.17.0 wave 7, seat FIX)
sblattj Sep 25, 2026
bbd5955
docs: say where deterministic findings leave the model path
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
32 changes: 23 additions & 9 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ PRXREF_LLM_REASONING_EFFORT=
# account. Must be > 0.
# PRXREF_LLM_CLI_CONCURRENCY=2

# Times a reply that is empty, that fails to parse, or that lacks a
# "findings" list is re-sent (default 1). Must be >= 0; 0 keeps only the
# single empty-reply retry. Each discarded attempt is written as
# <label>.attemptN.response.json when --trace-dir is set.
# PRXREF_LLM_PARSE_RETRIES=1

# Findings below this confidence floor are dropped (default 0.6).
# A probability: must be within [0.0, 1.0] inclusive.
PRXREF_CONFIDENCE_FLOOR=0.6
Expand Down Expand Up @@ -279,15 +285,23 @@ 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).
# Repository context (0.16.0): off (default) | diff | repo. off adds no
# repository-context entry, read, trace event or log line; chunk context's
# same-file definitions and dependency versions, Java and Kotlin ones
# included since 0.17.0, do not depend on it. diff adds definitions from the
# PR's own changed files (cross-chunk and diff-file entries, Java and Kotlin
# type declarations included): those files are read at the PR head through
# the forge's repository reader, or --repo-dir, when one is available, and
# without a reader the entries are built from the diff's hunk lines alone,
# with no warning. repo also reads files outside the diff -- import,
# path-convention and name-search definitions, contract excerpts and, since
# 0.17.0, readers (shared-state entries: excerpts of files outside the diff
# that read state the chunk's added lines write, found only with a file
# listing and ranked last, so the budget cuts them first) -- through the same
# reader, and logs one WARNING when there is no reader or no file listing.
# Only the chunk workers' prompts carry repository context; the whole-PR
# sweep prompt is unchanged. 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
Expand Down
224 changes: 223 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,227 @@ 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.17.0] — 2026-09-25

Java and Kotlin chunk context (#20), a bounded retry for unusable model replies
(#21), and code that reads the state a change writes (#22, parts 2 and 3). A
chunk worker now sees, for a changed Java or Kotlin file, the definitions its
added lines reference from the rest of that file and the Maven or Gradle
versions of what they import, as it already did for JavaScript, TypeScript and
Python. A reply that cannot be used as a review is sent again, up to
`PRXREF_LLM_PARSE_RETRIES` times (default `1`). At `PRXREF_REPO_CONTEXT=repo`,
a chunk also sees excerpts of unchanged code that reads state its added lines
write. A new deterministic check, always on, flags a toggle that ships on
while the pull request's own test setup turns it off. Repository context
at `off` still adds nothing, but Java and Kotlin chunk context does not depend
on it, so a pull request that changes a `.java`, `.kt` or `.kts` file can get
new prompt blocks and forge reads with every setting at its default.
`PRXREF_LLM_PARSE_RETRIES=0` handles replies exactly as 0.16.0 did. The run
record and `--format json` gain one key, `parse_retries`.

### Added

- **Java and Kotlin definitions in chunk context (#20).** The `### Definitions
referenced by this chunk` block now covers `.java`, `.kt` and `.kts` files
at every `PRXREF_REPO_CONTEXT` level. Java definitions are types, methods,
fields and constants, and enum constants; Kotlin definitions are types
(`class` in every flavour, `interface`, `fun interface`, `object` and
`typealias`), `fun`, extension functions included, and `val` and `var`. An
entry starts at up to 2 annotation lines directly above the definition, its
line number is the first annotation's, and those lines count toward the
6-line entry cap. The block's other caps are unchanged: 40 entries, 8000
characters, and files over 512 KiB skipped.
- **Maven and Gradle dependency versions (#20).** The `### Dependency
versions` block now lists `groupId:artifactId@version` for the imports on a
changed Java or Kotlin file's added lines. The build file is found by
walking up from the file's directory to the repository root, trying
`pom.xml`, then `build.gradle.kts`, then `build.gradle` at each level; the
first non-empty one wins. A `pom.xml` is resolved through its
`<properties>`, its parent chain inside the repository (at most 5 parents),
`<dependencyManagement>` and imported BOMs. A Gradle build file is read in
string notation (`"g:a:v"` and `"g:a"`), with `platform`,
`enforcedPlatform` and `mavenBom` as BOM owners and the version catalog
`libs.versions.toml`, which is read only when the build file mentions
`libs.`. A version that a BOM, a parent outside the repository or a Gradle
platform supplies reads `groupId:artifactId@(managed by <owner>)`. Imports
under `java`, `jdk`, `sun` and `kotlin` are skipped (`javax` is kept), and
so are imports under the project's own groupId or Gradle `group`. A
dependency matches an import when its groupId, of at least 2 segments, is a
package prefix of the import or shares at least 3 leading segments with it.
Among several matches, the artifactId that names a segment of the import
wins, so `com.fasterxml.jackson.databind` gives `jackson-databind`, and a
tie lists every tied artifact. A build file that cannot be parsed
contributes nothing and never fails a review, and a `pom.xml` that holds a
`<!DOCTYPE` or `<!ENTITY` declaration, or is larger than 512 KiB, is
refused before the XML parser runs.
- **Kotlin in repository context (#20).** At `diff` and `repo`, `.kt` and
`.kts` files are searched for Kotlin type declarations (`class` in every
flavour, `interface`, a named `object` and `typealias`), as Java files are
for Java types. Neither language gets a method, function or property here.
- **`PRXREF_LLM_PARSE_RETRIES` (#21).** A chunk or whole-PR sweep reply that
cannot be used as a review is sent again, as the same request with the same
prompt and budget, up to `PRXREF_LLM_PARSE_RETRIES` times: default `1`, at
least `0`, with no upper bound. One budget covers every kind of unusable
reply: an empty one, one that does not parse, one that parses to something
other than a JSON object and, at `1` or more, an object without a `findings`
list. An empty reply keeps the one retry 0.16.0 gave it, even at `0`. A
reply the provider stopped at the budget (`finish_reason` `length` or
`max_tokens`) is never retried, because its error already names
`PRXREF_LLM_MAX_TOKENS`, and neither is a call that raised. Each retry is a
new call that walks the model fallback chain again and logs one WARNING
ending `parse retry <k> of <N>`. Token counts and elapsed time cover every
call, and the unit's reported cost is their sum, or unknown when any call
reported none. Once the retries are spent, the unit fails with the last
reply's error, worded as in 0.16.0. `prxref review`, and so the webhook
server and `prxref eval run`, reads the variable; `orchestrate_review`,
`review_chunk` and `review_systemic` default to `0` for library callers.
- **`parse_retries` in the run record and the trace (#21).** The run record
and `--format json` gain `parse_retries`, right after `repo_context`: the
retries summed over every chunk and the sweep. It is `0` when nothing was
sent again, including an exit before any review unit ran, and `null` when
`PRXREF_LLM_PARSE_RETRIES` is `0`. With `PRXREF_TRACE_DIR` set and the
variable at `1` or more, a unit that retried keeps each discarded reply as
`<unit>.attempt<K>.response.json` (K from 1) beside its four trace files,
which still show the reply that was used, and its `<unit>.meta.json` gains
`parse_retries` and `first_error`, the error the first reply would have
failed the unit with.
- **A parse retry for the `prxref eval` judge (#21).** `prxref eval score`
reads `PRXREF_LLM_PARSE_RETRIES` (default `1`) from its own environment. A
judge reply the parser rejects for any reason (empty, not JSON, not an
object, no `grades` list, or a grades row that is not an object, has no
`human_id` or has an unknown grade) is sent again, the same request, up to
that many times. Unlike a review unit's, a truncated judge reply is
retried, and an empty one gets no retry at `0`. A call that raised and a
cache hit are never retried, and only the graded reply is cached.
`score.json`'s `judge` block gains `parse_retries` after `llm_calls`, which
counts every attempt, and `score.md`'s judge cost line adds `(1 parse
retry)` or `(<n> parse retries)` only when there were any. The judge's
`cost_usd` and the `judge_cost` metric count every attempt, and a case
whose retry raised after a rejected reply is priced by that reply. A traced
judge keeps each rejected reply as `judge.attempt<K>.response.json`, and
`judge.meta.json` gains `parse_retries` and `first_error`. `prxref eval
run` records `llm_parse_retries` in `run.json`'s `config`, which now holds
17 settings.
- **Code elsewhere that reads state this chunk writes (#22).** At
`PRXREF_REPO_CONTEXT=repo`, with a repository reader and a file listing, a
chunk worker also gets excerpts of unchanged code that reads state the
chunk's added lines write, such as a map the change stores a new object in,
or a table it appends rows to. The keys come from subscript stores
(`recv[k] = v`, key `recv`) and from calls of `append`, `add`, `insert`,
`save`, `put`, `push`, `store`, `write`, `extend`, `update` or `setdefault`
(key: the receiver's last segment); a local alias such as `data =
run.root().data` resolves to its attribute, and a key the added lines
assign a new value is skipped. A read is `.key` or `["key"]` used other
than as a store, or `key.<method>(` with a method that is not one of those
verbs, in a listed file of the same language that the pull request does not
change; files sharing the most leading directories with the change come
first. Each excerpt runs from the enclosing definition to the read, at most
8 lines. A chunk gets at most 6 such entries and tries at most 24 files,
within the same 16-read chunk cap and 200-read run cap, and only after the
other sources have made their reads. The entries form a new last prompt
block, `### Code elsewhere that reads state this chunk writes`, share the
chunk's `PRXREF_REPO_CONTEXT_MAX_CHARS` budget, and rank last, so the
budget cuts them first. An excluded path is never read. In the run record
their kind is `reader` and their reason `shared-state`. `off` and `diff`
get no such block.
- **Default-on toggles pinned off in tests (#22).** A new deterministic check,
always on, posts one `warning` at confidence 1.0 when the pull request adds
a toggle whose default is on and also adds a line to a suite-wide test
setup file (`conftest.py`, `setupTests.*`, `jest.setup.*` or
`vitest.setup.*`) that turns it off: the passing suite then never runs the
shipped default. The finding sits on the toggle's line, names every file
that pins it, and ends with `(deterministic check, no model)`. See
`docs/quality.md`.

### Changed

- **An object without `findings` is no longer a clean review (#21).** At the
default `PRXREF_LLM_PARSE_RETRIES=1`, a reply such as `{}` costs a second
call, and if that reply has no `findings` list either, the unit fails with
`worker review JSON has no findings list`, where 0.16.0 counted it as a
review with no findings. A reply that does not parse, or is not a JSON
object, also gets a second call, and the unit fails only when that reply
cannot be used either. A custom worker or systemic template whose reply
format drops `findings` fails every unit this way, and no template check
catches it (see `docs/prompt-templates.md`). `0` restores the 0.16.0
handling.
- **Java and Kotlin files are read at every level (#20).** Chunk context can
read each changed Java or Kotlin file, and the build files its dependency
walk tries, through the forge at the PR head whenever the forge can read
files and the pull request has a head sha, with `PRXREF_REPO_CONTEXT` at
`off` too. These reads are cached per run and are not counted against the
repository-context read caps.
- **A Java file's own definitions are shown once (#20).** With a repository
reader, the definitions a Java file of the chunk holds for the names its own
added lines mention now come from chunk context only, and are no longer
repeated as `diff-file` repository-context entries; Kotlin files work the
same way. A Java or Kotlin file of the chunk whose own added lines mention
every name the cross-chunk search wants is no longer read by that search.
- **`read_cap_hit` can be true in more runs (#22).** At `repo`, the
shared-state search spends the reads the other sources leave, and a read the
cap refuses counts, so `read_cap_hit` in the run record, the `repo_context`
trace event and `cap_hit=yes` on the `-v` line can be true in a run where
the definitions and contract excerpts alone fit the cap.

### Fixed

- **A definition from another file of the same chunk.** With a
repository reader, repository context at `diff` and `repo` now finds a
definition that one Python or JavaScript/TypeScript file of a chunk
references and another file of the same chunk defines outside its hunks.
0.16.0 left the chunk's own Python and JavaScript/TypeScript files out of
that search entirely.
- **A deterministic check's finding keeps its own severity.** Severity
consistency could raise the release-shape or pinned-off toggle finding to
the severity of a model finding in the same file that shared a rare code
token with it, or of a model finding with the same normalized title, so a
finding that ends `(deterministic check, no model)` could post at a
severity a model chose. The toggle finding was seen posted as an `error`
that way. Both checks' findings now take no part in severity consistency:
they are never raised, never raise another finding, and their text no
longer counts toward a code token's rarity.

### Known limitations

- **The follow-up lookup of #22 is not built.** Part 1 of #22, one bounded
extra call that looks up the symbol a below-floor finding says it could not
see, is deferred, and #22 stays open.
- **A Java or Kotlin file can be fetched twice.** Chunk context and
repository context keep separate readers with no shared cache, so a file
both of them read costs two requests per run, as 0.16.0's dependency and
same-file blocks already did for other languages.
- **Kotlin standard-library names cost lookups.** Names such as `Int`, `Unit`
or `Pair` are not in the built-in list of platform names repository context
ignores, so at `repo` each one a chunk mentions is looked for by the
file-name search, as a project type would be. Repository context has no
Kotlin import or path rules: outside the diff, a Kotlin type is found by the
file-name search alone.
- **Precompiled Gradle script plugins are treated as Kotlin.** Only
`build.gradle.kts` and `settings.gradle.kts` are skipped by the dependency
lookup; another `*.gradle.kts` file is handled like any Kotlin source, so its
Gradle API imports cost a walk for the nearest build file.
- **Some dependencies are not matched or not resolved.** An artifact whose
groupId is not a prefix of its packages gets no line: Guava, Lombok, JUnit 4,
Spring Boot starters and kotlinx among them. Gradle map notation
(`group: 'g', name: 'a'`) is not read, and a version set through a variable
or `gradle.properties` is not resolved.
- **A truncated review reply is not retried.** A reply stopped at
`PRXREF_LLM_MAX_TOKENS` that cannot be used fails the unit on the first
call, with the budget named, whatever `PRXREF_LLM_PARSE_RETRIES` says.
- **The timeout retry records its own parse retries only.** When a chunk is
run again after a timeout, only the second run's `parse_retries` is
counted, the same gap its token counts and cost have.
- **`score.json` does not total the reviews' parse retries.** It counts the
judge's; each case's own run record carries its review's `parse_retries`.
- **The shared-state search matches names, not types.** A reader is any line
that reads a key of the same name in a file of the same language, so an
unrelated `.data` or `table` elsewhere can fill an entry, and a reader in
another language is never found.
- **The toggle check needs both lines in the pull request.** A new pin on a
toggle that already existed, or a new toggle that an existing setup line
pins, is not reported, and neither is a pin outside the four suite-wide
setup file names.

## [0.16.0] — 2026-09-25

The repository-context release (#17). A chunk worker can now see code outside
Expand Down Expand Up @@ -1737,7 +1958,8 @@ 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.16.0...HEAD
[Unreleased]: https://github.com/sblattj/prxref/compare/v0.17.0...HEAD
[0.17.0]: https://github.com/sblattj/prxref/releases/tag/v0.17.0
[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
Expand Down
Loading
Loading