From e971f4490e9a6c8c23d9e645e47232632cd015e3 Mon Sep 17 00:00:00 2001
From: Jacob Metoyer <150724801+Jacob-Met@users.noreply.github.com>
Date: Thu, 8 Oct 2026 08:40:58 -0700
Subject: [PATCH] Add explicit named function targets with supplied diff
context
Allow repeated --target path.py::qualname on targets and run, selecting only the caller's requested functions. Preserve automatic selection and default outputs, use native containment/encoding/discovery, retain genuine added-line coverage, and provide the exact loaded diff plus selected source to planning, generation and repair.
Publish the eight independently received source/test/doc paths on current full parent f3b2135cad31852b599350564fe76dd160a6a522, preserving all 1037 unrelated parent leaves. Retain the 63-file receiving packet, original negative controls, paired native baseline limitations, complete scoped source history, actual model messages and independent native acceptance.
Native accepted source: 00b63c8e03cc515160da1202331224167d12d253. The current parent's module-identity correction remains intact. Hosted full-tree CI is separate from the pinned native receiving.
---
README.md | 9 +
docs/EXPLICIT_TARGETS.md | 111 ++
.../explicit-targets-a219f250962c/README.md | 95 ++
.../SHA256SUMS.json | 317 +++++
.../assessment-result.json | 198 ++++
.../baseline-assessment/assessment.log | 1 +
.../baseline-assessment/assessment.py | 71 ++
.../default_binding-run/report.html | 92 ++
.../default_binding-run/report.json | 23 +
.../default_binding-run/report.md | 9 +
.../default_binding-run/testpilot.patch | 0
.../module_constant-run/report.html | 92 ++
.../module_constant-run/report.json | 23 +
.../module_constant-run/report.md | 9 +
.../module_constant-run/testpilot.patch | 0
.../snapshot/testpilot/__init__.py | 7 +
.../snapshot/testpilot/__main__.py | 117 ++
.../snapshot/testpilot/_pytest_generated.py | 75 ++
.../snapshot/testpilot/diff.py | 258 +++++
.../snapshot/testpilot/html_report.py | 327 ++++++
.../snapshot/testpilot/loop.py | 528 +++++++++
.../snapshot/testpilot/model.py | 241 ++++
.../snapshot/testpilot/sandbox.py | 273 +++++
.../baseline-assessment/source-manifest.json | 54 +
.../checks/authored-pytest-result.json | 37 +
.../checks/authored-pytest.log | 77 ++
.../checks/baseline-platform-control.json | 23 +
.../checks/baseline-platform-control.log | 75 ++
.../checks/bundle-verify.log | 7 +
.../current-composition-pytest-result.json | 38 +
.../checks/current-composition-pytest.log | 28 +
.../checks/current-parent-control.json | 24 +
.../checks/current-parent-control.log | 27 +
.../checks/owner-sandbox-delta.patch | 39 +
.../checks/source-patch-receiving.json | 12 +
.../coordination/recheck-owner-note.json | 5 +
.../independent/REVIEW.md | 43 +
.../independent/SHA256SUMS.json | 105 ++
.../model-calls/explicit-run-model-calls.json | 46 +
.../model-calls/refusal-0-model-calls.json | 1 +
.../model-calls/refusal-1-model-calls.json | 1 +
.../model-calls/refusal-2-model-calls.json | 1 +
.../model-calls/refusal-3-model-calls.json | 1 +
.../model-calls/refusal-4-model-calls.json | 1 +
.../model-calls/refusal-5-model-calls.json | 1 +
.../model-calls/refusal-6-model-calls.json | 1 +
.../model-calls/refusal-7-model-calls.json | 1 +
.../model-calls/refusal-8-model-calls.json | 1 +
.../model-calls/refusal-9-model-calls.json | 1 +
.../independent/native-run/report.html | 174 +++
.../independent/native-run/report.json | 312 +++++
.../independent/native-run/report.md | 30 +
.../independent/native-run/testpilot.patch | 13 +
.../independent/receive_explicit_targets.py | 322 ++++++
.../independent/receiving-result.json | 1017 +++++++++++++++++
.../independent/source-binding.json | 225 ++++
.../independent/source-freeze.json | 203 ++++
.../source/authored-source-pin.json | 59 +
.../source/current-baseline-manifest.json | 146 +++
.../source/explicit-targets.patch | 650 +++++++++++
.../source/frozen-source.json | 203 ++++
.../source/original-baseline-manifest.json | 131 +++
.../source/publication-mapping.json | 79 ++
.../source/receiving-manifest.json | 95 ++
.../source/source-history.bundle | Bin 0 -> 76638 bytes
testpilot/__main__.py | 31 +-
testpilot/html_report.py | 4 +
testpilot/loop.py | 42 +-
testpilot/targets.py | 120 ++
tests/test_explicit_target_cli.py | 41 +
tests/test_explicit_targets.py | 102 ++
71 files changed, 7510 insertions(+), 15 deletions(-)
create mode 100644 docs/EXPLICIT_TARGETS.md
create mode 100644 docs/receiving/explicit-targets-a219f250962c/README.md
create mode 100644 docs/receiving/explicit-targets-a219f250962c/SHA256SUMS.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/baseline-assessment/assessment-result.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/baseline-assessment/assessment.log
create mode 100644 docs/receiving/explicit-targets-a219f250962c/baseline-assessment/assessment.py
create mode 100644 docs/receiving/explicit-targets-a219f250962c/baseline-assessment/default_binding-run/report.html
create mode 100644 docs/receiving/explicit-targets-a219f250962c/baseline-assessment/default_binding-run/report.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/baseline-assessment/default_binding-run/report.md
create mode 100644 docs/receiving/explicit-targets-a219f250962c/baseline-assessment/default_binding-run/testpilot.patch
create mode 100644 docs/receiving/explicit-targets-a219f250962c/baseline-assessment/module_constant-run/report.html
create mode 100644 docs/receiving/explicit-targets-a219f250962c/baseline-assessment/module_constant-run/report.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/baseline-assessment/module_constant-run/report.md
create mode 100644 docs/receiving/explicit-targets-a219f250962c/baseline-assessment/module_constant-run/testpilot.patch
create mode 100644 docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/__init__.py
create mode 100644 docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/__main__.py
create mode 100644 docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/_pytest_generated.py
create mode 100644 docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/diff.py
create mode 100644 docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/html_report.py
create mode 100644 docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/loop.py
create mode 100644 docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/model.py
create mode 100644 docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/sandbox.py
create mode 100644 docs/receiving/explicit-targets-a219f250962c/baseline-assessment/source-manifest.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/checks/authored-pytest-result.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/checks/authored-pytest.log
create mode 100644 docs/receiving/explicit-targets-a219f250962c/checks/baseline-platform-control.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/checks/baseline-platform-control.log
create mode 100644 docs/receiving/explicit-targets-a219f250962c/checks/bundle-verify.log
create mode 100644 docs/receiving/explicit-targets-a219f250962c/checks/current-composition-pytest-result.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/checks/current-composition-pytest.log
create mode 100644 docs/receiving/explicit-targets-a219f250962c/checks/current-parent-control.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/checks/current-parent-control.log
create mode 100644 docs/receiving/explicit-targets-a219f250962c/checks/owner-sandbox-delta.patch
create mode 100644 docs/receiving/explicit-targets-a219f250962c/checks/source-patch-receiving.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/coordination/recheck-owner-note.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/independent/REVIEW.md
create mode 100644 docs/receiving/explicit-targets-a219f250962c/independent/SHA256SUMS.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/independent/model-calls/explicit-run-model-calls.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-0-model-calls.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-1-model-calls.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-2-model-calls.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-3-model-calls.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-4-model-calls.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-5-model-calls.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-6-model-calls.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-7-model-calls.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-8-model-calls.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-9-model-calls.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/independent/native-run/report.html
create mode 100644 docs/receiving/explicit-targets-a219f250962c/independent/native-run/report.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/independent/native-run/report.md
create mode 100644 docs/receiving/explicit-targets-a219f250962c/independent/native-run/testpilot.patch
create mode 100644 docs/receiving/explicit-targets-a219f250962c/independent/receive_explicit_targets.py
create mode 100644 docs/receiving/explicit-targets-a219f250962c/independent/receiving-result.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/independent/source-binding.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/independent/source-freeze.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/source/authored-source-pin.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/source/current-baseline-manifest.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/source/explicit-targets.patch
create mode 100644 docs/receiving/explicit-targets-a219f250962c/source/frozen-source.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/source/original-baseline-manifest.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/source/publication-mapping.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/source/receiving-manifest.json
create mode 100644 docs/receiving/explicit-targets-a219f250962c/source/source-history.bundle
create mode 100644 testpilot/targets.py
create mode 100644 tests/test_explicit_target_cli.py
create mode 100644 tests/test_explicit_targets.py
diff --git a/README.md b/README.md
index eaf87ec..3848e40 100644
--- a/README.md
+++ b/README.md
@@ -82,6 +82,15 @@ python -m testpilot run \
On a real repo, use `--git-base main` instead of `--diff FILE`. Exit codes: 0 means the generated tests pass, 1 means any other outcome, 2 means a config error.
+## Choose functions explicitly
+
+Use repeated `--target path.py::qualname` flags on `targets` or `run` to select
+specific current functions, including unchanged functions affected by a change
+you have identified. The supplied diff stays available as context to planning,
+generation and repair. Without this option, automatic diff selection is unchanged.
+See [explicit targets](docs/EXPLICIT_TARGETS.md) for preview, ordering, refusals,
+saved selection context and unchanged-function coverage semantics.
+
## Review your own run in a browser
Every run also writes **`report.html`** in its output directory. Open that file
diff --git a/docs/EXPLICIT_TARGETS.md b/docs/EXPLICIT_TARGETS.md
new file mode 100644
index 0000000..3cfe7a0
--- /dev/null
+++ b/docs/EXPLICIT_TARGETS.md
@@ -0,0 +1,111 @@
+# Explicit function targets
+
+Use this workflow when you know which function needs tests, including a function
+whose body did not change. For example, changing a module-level limit can change
+a function's behavior while the normal diff selector finds no changed function.
+
+Preview the current source before generating:
+
+~~~bash
+python -m testpilot targets --repo . --diff change.diff \
+ --target settings.py::accepts --json
+
+python -m testpilot run --repo . --diff change.diff \
+ --target settings.py::accepts \
+ --target service.py::Service.apply \
+ --script responses --out out/selected
+~~~
+
+Use the same repeatable option with `--git-base REF`, or `--diff -` for stdin.
+A diff input remains required; an empty diff is permitted when you deliberately
+want tests for current functions without a recorded code change.
+
+## Selection and preview
+
+With `--target`, TestPilot selects exactly the requested current functions in
+the order supplied. It does not add automatically changed functions. Repeating
+the same resolved source/function identity selects it once, at its first
+position. The `selection.requested` record preserves every original argument,
+including repetitions and spelling, while function records use normalized
+repository-relative paths.
+
+Use a repository-relative Python file followed by `::` and its exact qualified
+name. A method is `file.py::Class.method`; nested classes can add more components.
+The existing AST discovery also supports async functions and definitions inside
+module/class `if`, `try`, `with` and `match` blocks. Nested functions belong to
+their enclosing function and cannot be selected separately. The final `::`
+separates the path from the name; quote the complete argument in your shell when
+the path needs it.
+
+A name must identify one source definition. Multiple definitions with the same
+qualified name are refused, including definitions in mutually exclusive branches
+or property getter/setter definitions. TestPilot does not guess which definition
+will exist at runtime.
+
+Missing files/names, malformed specs, non-Python and test sources, absolute or
+parent-traversing paths, and symlinks resolving outside the repository are
+refused before baseline tests or model calls. A normal-looking alias to a test
+source is also refused. Inside-repository source symlinks follow the existing
+containment rule. Python source is decoded with Python's encoding-cookie/BOM
+rules; selecting source never imports or executes it.
+
+The existing `targets` preview remains read-only: no tests, generated files,
+model calls or output directories. Text output says "caller-selected"; `--json`
+includes the exact selected source records plus the selection context. A preview
+does not reserve files: `run` reads the current source again.
+
+## Context and saved results
+
+Explicit mode sends the exact current selected function source and the exact
+diff string received by `TestPilot.run` to the planner, editor and every repair
+call. It identifies the functions as caller-selected and potentially unchanged.
+The CLI retains its existing raw-byte diff loader, including universal-newline
+normalization and surrogate escaping; recorded `diff_text` is that loaded
+string, not a reconstruction or a byte-for-byte copy of the original diff file.
+
+The existing `changed_functions` JSON field remains the selected native source
+record list for compatibility. Explicit previews and run reports additionally
+include:
+
+~~~json
+{
+ "selection": {
+ "mode": "explicit",
+ "requested": ["settings.py::accepts"],
+ "reason": "Caller selected these targets; no dependency inference was performed.",
+ "diff_text": ""
+ }
+}
+~~~
+
+Markdown labels caller-selected functions and the reason. The offline HTML view
+also shows the reason and an expandable literal diff context. Its original JSON
+download retains the complete selection record.
+
+Without `--target` (or with the Python API's default `targets=None`), the existing
+automatic selector, prompts, report/preview schema and output remain unchanged.
+The optional `selection` object is absent. Python callers can use
+`TestPilot.run(repo, diff_text, targets=["path.py::qualname"])`; an empty explicit
+sequence is an error. `testpilot.targets.resolve_targets(repo, diff_text, targets)`
+provides the same resolver, raising `TargetSelectionError` on a refused request.
+
+## Coverage and limits
+
+`changed_lines` still contains only actual added diff lines inside each selected
+function's current span. An unchanged function therefore has an empty list.
+Selecting it does not turn all of its lines into changed lines. Total coverage
+can improve, while the changed-line population is zero and its percentages stay
+unavailable. Unrelated or refused diff source paths are context only in explicit
+mode and are never opened or added as targets.
+
+This is a deliberate selection workflow. It does not infer dependencies, prove
+that all affected functions were selected, or supply surrounding module values
+that are absent from both the selected source and the supplied diff. A developer
+must provide useful context and review the resulting tests. The existing
+sandbox, generated-file preservation, model routing, token budget, execution and
+patch contracts apply unchanged.
+
+Receiving uses deterministic `ScriptedModel` responses with actual outgoing
+message checks, subprocess tests and patch application. Those controls qualify
+the workflow and context delivery; they do not establish live-model generation
+quality.
diff --git a/docs/receiving/explicit-targets-a219f250962c/README.md b/docs/receiving/explicit-targets-a219f250962c/README.md
new file mode 100644
index 0000000..3f3a290
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/README.md
@@ -0,0 +1,95 @@
+# Explicit named targets — source receiving packet
+
+Repository: https://github.com/Jacob-Met/testpilot
+Claim: https://github.com/Jacob-Met/testpilot/issues/30
+Comparison coordination: https://github.com/Jacob-Met/testpilot/issues/26#issuecomment-6061973659
+
+This packet preserves a native developer capability: repeated --target path.py::qualname
+on targets and run selects exactly the requested current functions. It carries the
+exact loaded diff and selected function source through planning, generation and repair.
+Default automatic selection and output remain unchanged. Selection is deliberate;
+no dependency completeness or live-model generation quality is claimed.
+
+## Exact source
+
+Current canonical receiving base: 63174afd8e63a0ea562cf188b7d971522f017a30,
+tree cea947d9aeb2253466593044889a1bb548f8b7c4.
+Frozen scoped source: 00b63c8e03cc515160da1202331224167d12d253,
+tree d1ba76e3872a0e843821b799f8accb69aa32416d.
+Scoped baseline: 33a10cd5b756b8ade8768bd6f8da75f55da369c3.
+
+The scoped materialization contains 27 files, not the complete canonical repository.
+Its complete local source history is preserved in source/source-history.bundle.
+Do not merge this snapshot ancestry into the canonical repository.
+
+source/explicit-targets.patch contains only the eight claimed source/test/doc
+deltas over the current receiving base. source/receiving-manifest.json lists every
+required preimage and postimage. Ordinary git apply --index --check followed by
+git apply --index in an isolated exact baseline received all 27 scoped leaves
+byte-for-byte, preserving all 19 untouched baseline leaves. Keep all other
+canonical repository files when composing the source branch. If current main has
+changed these preimages, reconcile the exact additive seams and record a new pin.
+
+The saved-test recheck implementation, sandbox exact_files option, documents and
+tests are the owner's exact 631 source. All three recheck CLI additions are preserved.
+The raw diff loader, function discovery, generated-file allocation, fence/patch
+functions, execution and coverage contracts remain unchanged.
+
+## Authored native qualification
+
+Existing Python3.12.8 / pytest9.1.1 / coverage7.16.2 was used read-only with
+PYTHONDONTWRITEBYTECODE=1 and PYTEST_DISABLE_PLUGIN_AUTOLOAD=1. No installation
+was performed. All 12 new explicit-target tests pass in both source compositions,
+including real ScriptedModel generation plus repair and exact outgoing prompt
+context assertions.
+
+The original e228 composition is retained at fc24a55d2c5e2d94478072e8e6736bf44bab81a6.
+Its selected regression run passed 126 tests and 18 subtests. Two unchanged
+non-UTF-8 filename fixtures failed at Path.write_bytes with macOS errno92 before
+TestPilot ran. The identical two failures were reproduced against untouched e228
+source. Raw results are retained; the fixtures were not changed.
+
+The current631 composition's five-module control passed 58 tests and 18 subtests,
+with one existing skip. One unchanged saved-test recheck test expected one total
+passing case and observed two; its retained generated pass and failing configured
+case otherwise matched. The exact same failure was reproduced in untouched631
+source. This is an inherited native receiving limitation, not a green canonical
+suite claim and not a recheck repair in this contribution.
+
+checks/ retains both original runs and their bounded paired baseline controls.
+baseline-assessment/ retains the original real no_changes/unsupported-option CLI
+witnesses and all eight exact e228 source modules. No known test failures have been
+removed or silently reclassified as passes.
+
+## Independent receiving
+
+Independent root source and native receiving ACCEPT the exact frozen 00b source.
+All six groups pass on an existing Python3.13.7 / pytest9.1.1 / coverage7.16.2
+environment. The receiver records 23 actual CLI/Git/pytest commands, three actual
+ScriptedModel calls including repair with exact diff and selected source in each
+message, ten refused selections before calls/output, Unicode/Latin-1 read-only
+selection, exact default JSON parity, and a patch that applies and runs three tests.
+The unchanged 21-file independent packet (137,606 bytes) is retained in independent/,
+including all original messages, outputs, receiver source, source hashes and ACCEPT.
+Its SHA256SUMS.json is 17d33526a6b44439e691f9b5cc503d172c15be86a7d83e90867a1b209c8ff6b2.
+
+## Canonical publication mapping
+
+Fresh public parent: f3b2135cad31852b599350564fe76dd160a6a522,
+tree d0107a6f8b3b8cc00dadebc5f794060f275f3f5b. All eight author-path preimages
+still match the received 631 baseline. Publish the eight accepted source leaves and
+this unique receiving directory on that full parent tree; preserve every other
+parent leaf. The actual public commit/tree and hosted CI remain separately recorded.
+
+The parent added its own PR32 module_name condition correction in untouched diff.py,
+retaining the lib package prefix. Exact source comparison verifies that one condition
+is the only diff.py change from 631. This contribution continues to call the existing
+native functions_touching/module_name implementation and preserves that current owner
+source. source/publication-mapping.json records this distinction. The native receiving
+claim stays pinned to 631; complete combined-tree hosted CI is a separate qualification.
+
+The source-history bundle preserves scoped native history for source custody.
+Canonical publication uses the full current parent and does not merge snapshot ancestry.
+No service, resident worker, goal, database or live-model activation is implied.
+
+Native final assembly was held by the declared free-space floor. Existing native draft and independent packets remain preserved separately. These final receiving bytes are published directly through the canonical GitHub source tree; no finalized native archive or unified native custody is claimed.
diff --git a/docs/receiving/explicit-targets-a219f250962c/SHA256SUMS.json b/docs/receiving/explicit-targets-a219f250962c/SHA256SUMS.json
new file mode 100644
index 0000000..8e52747
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/SHA256SUMS.json
@@ -0,0 +1,317 @@
+{
+ "schema": "testpilot.explicit-targets.receiving.v1",
+ "native_source_commit": "00b63c8e03cc515160da1202331224167d12d253",
+ "public_parent": "f3b2135cad31852b599350564fe76dd160a6a522",
+ "files": [
+ {
+ "path": "README.md",
+ "bytes": 5870,
+ "sha256": "f7f1ae09fa663dae857c8cb6a8012064b5f9767ff52ce2b593991db7712e4439"
+ },
+ {
+ "path": "baseline-assessment/assessment-result.json",
+ "bytes": 9395,
+ "sha256": "51cd7fc0e3fc640ba413185d6448c5a784dd4f45b14dcc354ac67dbb531874c0"
+ },
+ {
+ "path": "baseline-assessment/assessment.log",
+ "bytes": 986,
+ "sha256": "fe62962b76f9a76eefc6711a751d4e58e1554aec1bd8d3ffee8dc5bea7c9aa51"
+ },
+ {
+ "path": "baseline-assessment/assessment.py",
+ "bytes": 6045,
+ "sha256": "79515987d225fe2041e25125bfbdef9ac0d327ef1f54f7d5553111078350050c"
+ },
+ {
+ "path": "baseline-assessment/default_binding-run/report.html",
+ "bytes": 9593,
+ "sha256": "2acf36aba5e16184a32eda62802bee0e6b92e333a5a1e282114e54e2a465703d"
+ },
+ {
+ "path": "baseline-assessment/default_binding-run/report.json",
+ "bytes": 476,
+ "sha256": "e5d1bb92c30c3ba4e074a064ae3f968d80e350be9386ddde3ce50df2d80592f7"
+ },
+ {
+ "path": "baseline-assessment/default_binding-run/report.md",
+ "bytes": 213,
+ "sha256": "b071ad507925b32f0ec36311eea4646ef6d3cc315bc48a17aadd29753959722c"
+ },
+ {
+ "path": "baseline-assessment/default_binding-run/testpilot.patch",
+ "bytes": 0,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ {
+ "path": "baseline-assessment/module_constant-run/report.html",
+ "bytes": 9593,
+ "sha256": "2acf36aba5e16184a32eda62802bee0e6b92e333a5a1e282114e54e2a465703d"
+ },
+ {
+ "path": "baseline-assessment/module_constant-run/report.json",
+ "bytes": 476,
+ "sha256": "e5d1bb92c30c3ba4e074a064ae3f968d80e350be9386ddde3ce50df2d80592f7"
+ },
+ {
+ "path": "baseline-assessment/module_constant-run/report.md",
+ "bytes": 213,
+ "sha256": "b071ad507925b32f0ec36311eea4646ef6d3cc315bc48a17aadd29753959722c"
+ },
+ {
+ "path": "baseline-assessment/module_constant-run/testpilot.patch",
+ "bytes": 0,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ {
+ "path": "baseline-assessment/snapshot/testpilot/__init__.py",
+ "bytes": 390,
+ "sha256": "65c63b80d7da34a83a6049a79c24b8335878cd49cc79951fdefc5f1532ae841f"
+ },
+ {
+ "path": "baseline-assessment/snapshot/testpilot/__main__.py",
+ "bytes": 5275,
+ "sha256": "83caf8df097902120a0cfbd6adb897d5b6be7ba2407b0217606e7038599b5a5f"
+ },
+ {
+ "path": "baseline-assessment/snapshot/testpilot/_pytest_generated.py",
+ "bytes": 2943,
+ "sha256": "d65352d7bade965d7b9dc73efe5d4d01844fa8c7474a1bdcdb3b708f6decf768"
+ },
+ {
+ "path": "baseline-assessment/snapshot/testpilot/diff.py",
+ "bytes": 9396,
+ "sha256": "659021d4a4b462b86eadc25957407b683c733fec60ae33c0703295cb7640a06c"
+ },
+ {
+ "path": "baseline-assessment/snapshot/testpilot/html_report.py",
+ "bytes": 20656,
+ "sha256": "183703d05f32552f7f7c39aa958c39bae61d824e3fa9cd6a6dd686f0d4c228a4"
+ },
+ {
+ "path": "baseline-assessment/snapshot/testpilot/loop.py",
+ "bytes": 24064,
+ "sha256": "990d6df8b65877e6f25e319771ccad189767575587b4a165f3c3a8e2280c4c93"
+ },
+ {
+ "path": "baseline-assessment/snapshot/testpilot/model.py",
+ "bytes": 10175,
+ "sha256": "f6ae697f1f5d686ffdcb96f5451d1a9600d7d41c7bcf324d7e8c276a107e8673"
+ },
+ {
+ "path": "baseline-assessment/snapshot/testpilot/sandbox.py",
+ "bytes": 11877,
+ "sha256": "2e852248ffc238371cf2b11b5797a88c519330db359b2cb969bc61aa72ef1931"
+ },
+ {
+ "path": "baseline-assessment/source-manifest.json",
+ "bytes": 1932,
+ "sha256": "fb4d8d34fdf6490d349cf3b982963a81d659e6b091348aca88390c9b45d2edfb"
+ },
+ {
+ "path": "checks/authored-pytest-result.json",
+ "bytes": 2295,
+ "sha256": "9b94b6f738c5b75824edb01dbc583379be0e3b0d9c80e203fd8343b2228681f5"
+ },
+ {
+ "path": "checks/authored-pytest.log",
+ "bytes": 4924,
+ "sha256": "8331450b67791e32c2c73cce33990b5445fc0213d9c471ac7eb3294abd3883ab"
+ },
+ {
+ "path": "checks/baseline-platform-control.json",
+ "bytes": 1162,
+ "sha256": "114f2d8ef4430a3b27fe6f631a42e490d207fc72d22740c1d8df1d892322e427"
+ },
+ {
+ "path": "checks/baseline-platform-control.log",
+ "bytes": 4761,
+ "sha256": "6a6f94c23215b7af7ecdd2faba8a318557ea3105048d2dd627fd68205b301257"
+ },
+ {
+ "path": "checks/bundle-verify.log",
+ "bytes": 403,
+ "sha256": "68ce1dfd6a7c71bd375a18a75ee89913e91e50ee4c1eba8ca70fd0ae6cf6bec0"
+ },
+ {
+ "path": "checks/current-composition-pytest-result.json",
+ "bytes": 1879,
+ "sha256": "1e43cd8b045c8acc55bbf7a424846bb59e0deb549230aa6e113220cbfc059d18"
+ },
+ {
+ "path": "checks/current-composition-pytest.log",
+ "bytes": 1850,
+ "sha256": "8d4aad87fb875bc8d3521699103bb76269a777da8ab84b0eb2b985475916fe49"
+ },
+ {
+ "path": "checks/current-parent-control.json",
+ "bytes": 956,
+ "sha256": "12e04f0c20e9756c0a0a6930105c025ce7266e03a6a44b95836000aa3c724867"
+ },
+ {
+ "path": "checks/current-parent-control.log",
+ "bytes": 1737,
+ "sha256": "f85c73fa065e8ceadef1c940eb342c3f17f35c6711f4ed9d636f70ce627a453a"
+ },
+ {
+ "path": "checks/owner-sandbox-delta.patch",
+ "bytes": 2121,
+ "sha256": "f938e28a02a74d36360e630a2207e56bc4a9812734a458ecd5d46860a425fc9a"
+ },
+ {
+ "path": "checks/source-patch-receiving.json",
+ "bytes": 543,
+ "sha256": "0b9885f3ded06c7348a91fc572261397ed7be8d45922f225d2be62f9672f5eea"
+ },
+ {
+ "path": "coordination/recheck-owner-note.json",
+ "bytes": 1437,
+ "sha256": "ade3ee1b63c79c96bbf126701a51514e3ba25c164b0a19b08c4019dbea3585bd"
+ },
+ {
+ "path": "independent/REVIEW.md",
+ "bytes": 5325,
+ "sha256": "7ce638499b5ede479e7b4aa59abffaaa94ae2c1bcc9b1405a06476c6d2a16ef8"
+ },
+ {
+ "path": "independent/SHA256SUMS.json",
+ "bytes": 3344,
+ "sha256": "17d33526a6b44439e691f9b5cc503d172c15be86a7d83e90867a1b209c8ff6b2"
+ },
+ {
+ "path": "independent/model-calls/explicit-run-model-calls.json",
+ "bytes": 5220,
+ "sha256": "a92be45f0279b65a2ad5f614126c078d65f6c18246a9d835323546e2c6605a42"
+ },
+ {
+ "path": "independent/model-calls/refusal-0-model-calls.json",
+ "bytes": 3,
+ "sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570"
+ },
+ {
+ "path": "independent/model-calls/refusal-1-model-calls.json",
+ "bytes": 3,
+ "sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570"
+ },
+ {
+ "path": "independent/model-calls/refusal-2-model-calls.json",
+ "bytes": 3,
+ "sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570"
+ },
+ {
+ "path": "independent/model-calls/refusal-3-model-calls.json",
+ "bytes": 3,
+ "sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570"
+ },
+ {
+ "path": "independent/model-calls/refusal-4-model-calls.json",
+ "bytes": 3,
+ "sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570"
+ },
+ {
+ "path": "independent/model-calls/refusal-5-model-calls.json",
+ "bytes": 3,
+ "sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570"
+ },
+ {
+ "path": "independent/model-calls/refusal-6-model-calls.json",
+ "bytes": 3,
+ "sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570"
+ },
+ {
+ "path": "independent/model-calls/refusal-7-model-calls.json",
+ "bytes": 3,
+ "sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570"
+ },
+ {
+ "path": "independent/model-calls/refusal-8-model-calls.json",
+ "bytes": 3,
+ "sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570"
+ },
+ {
+ "path": "independent/model-calls/refusal-9-model-calls.json",
+ "bytes": 3,
+ "sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570"
+ },
+ {
+ "path": "independent/native-run/report.html",
+ "bytes": 33952,
+ "sha256": "5efe4f027412d299a10b73af853961f816900b850b4bdfac36ff5166cf6476b2"
+ },
+ {
+ "path": "independent/native-run/report.json",
+ "bytes": 9728,
+ "sha256": "7376864902ec48d609ce2b787033ae3e3a26148d55641ff1da378c78aa6f5dbb"
+ },
+ {
+ "path": "independent/native-run/report.md",
+ "bytes": 1017,
+ "sha256": "b79854ef4ba5821d117c9ff3b201bec8396e7d9158636e0f640d2dd65d7ee896"
+ },
+ {
+ "path": "independent/native-run/testpilot.patch",
+ "bytes": 336,
+ "sha256": "9e53e5ac4daa21d8ee93a51c94a60aec14fbee520d48ef683517fc250a456800"
+ },
+ {
+ "path": "independent/receive_explicit_targets.py",
+ "bytes": 17612,
+ "sha256": "08dc3a0c30cb559e53bc644de966b13dbcafb7bb3c841886963261e28832cf5d"
+ },
+ {
+ "path": "independent/receiving-result.json",
+ "bytes": 45690,
+ "sha256": "b47fab55bcc44de2179cfd10c91238b5d1557180d608e21ec6fb3768e4febdd7"
+ },
+ {
+ "path": "independent/source-binding.json",
+ "bytes": 7598,
+ "sha256": "f7e08b33eea94a82edfeb6fd61aac5b7d47608d3c2741e4bf6377519a38a703c"
+ },
+ {
+ "path": "independent/source-freeze.json",
+ "bytes": 7754,
+ "sha256": "e916c8da533841efde3666e3f3034a1d579315ec4ea54953f273f1b2db30b8d7"
+ },
+ {
+ "path": "source/authored-source-pin.json",
+ "bytes": 1787,
+ "sha256": "5e1c858695314e943c7299978e73ceb263f3cf81c6c65ad0c2e046956bd8bb1d"
+ },
+ {
+ "path": "source/current-baseline-manifest.json",
+ "bytes": 5326,
+ "sha256": "b731e4133a55a5d545801cebb7113b9b75e21f7e940abb3f9e31a9ae8aa3f2ff"
+ },
+ {
+ "path": "source/explicit-targets.patch",
+ "bytes": 32658,
+ "sha256": "61724e6514a5cf346259d93ee1937c929505fe9fafab0bb5fc51686e090cb870"
+ },
+ {
+ "path": "source/frozen-source.json",
+ "bytes": 7754,
+ "sha256": "e916c8da533841efde3666e3f3034a1d579315ec4ea54953f273f1b2db30b8d7"
+ },
+ {
+ "path": "source/original-baseline-manifest.json",
+ "bytes": 4880,
+ "sha256": "5ae651dcb97dc49d875a2cd8d5fa7668ccdc0daa2c2cb38ca2df9548b794078f"
+ },
+ {
+ "path": "source/publication-mapping.json",
+ "bytes": 3201,
+ "sha256": "9915f639e06660f184590f8958b6030f4574681627ff5e0671234c46b2ff3199"
+ },
+ {
+ "path": "source/receiving-manifest.json",
+ "bytes": 3295,
+ "sha256": "4ca21b62bafa5b4c0e5b9343d9c057bb7b669f9523c629b73d4861c65086ac5d"
+ },
+ {
+ "path": "source/source-history.bundle",
+ "bytes": 76638,
+ "sha256": "c7b9276b978b2f0574cce50493a2e706b3e5929ab792706987c773b08074d488"
+ }
+ ]
+}
diff --git a/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/assessment-result.json b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/assessment-result.json
new file mode 100644
index 0000000..3e8fa79
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/assessment-result.json
@@ -0,0 +1,198 @@
+{
+ "source_commit": "e2285d68b2ea5eb2158c0a7e0d936ce5d56f8ff5",
+ "python": "3.13.7 (v3.13.7:bcee1c32211, Aug 14 2025, 19:10:51) [Clang 16.0.0 (clang-1600.0.26.6)]",
+ "qualification": "Exact-source read-only product assessment; disposable synthetic Git fixtures only",
+ "cases": [
+ {
+ "name": "module_constant",
+ "before_source": "LIMIT = 100\n\n\ndef accepts(amount):\n return amount <= LIMIT\n",
+ "after_source": "LIMIT = 50\n\n\ndef accepts(amount):\n return amount <= LIMIT\n",
+ "git_diff": "diff --git a/settings.py b/settings.py\nindex 5ce271c..1cce5bd 100644\n--- a/settings.py\n+++ b/settings.py\n@@ -1,4 +1,4 @@\n-LIMIT = 100\n+LIMIT = 50\n \n \n def accepts(amount):\n",
+ "function": "accepts",
+ "argument": 75,
+ "before_result": true,
+ "after_result": false,
+ "target_count": 0,
+ "targets": [],
+ "run": {
+ "exit": 1,
+ "status": "no_changes",
+ "message": "diff touches no Python functions outside tests",
+ "model_calls": 0,
+ "tokens": 0,
+ "generated_files": 0,
+ "report_sha256": "e5d1bb92c30c3ba4e074a064ae3f968d80e350be9386ddde3ce50df2d80592f7"
+ },
+ "fixture_bytes_unchanged": true
+ },
+ {
+ "name": "default_binding",
+ "before_source": "LIMIT: int = 100\n\n\ndef cap(amount, limit=LIMIT):\n return min(amount, limit)\n",
+ "after_source": "LIMIT: int = 50\n\n\ndef cap(amount, limit=LIMIT):\n return min(amount, limit)\n",
+ "git_diff": "diff --git a/settings.py b/settings.py\nindex 58bc7f8..5c1b648 100644\n--- a/settings.py\n+++ b/settings.py\n@@ -1,4 +1,4 @@\n-LIMIT: int = 100\n+LIMIT: int = 50\n \n \n def cap(amount, limit=LIMIT):\n",
+ "function": "cap",
+ "argument": 150,
+ "before_result": 100,
+ "after_result": 50,
+ "target_count": 0,
+ "targets": [],
+ "run": {
+ "exit": 1,
+ "status": "no_changes",
+ "message": "diff touches no Python functions outside tests",
+ "model_calls": 0,
+ "tokens": 0,
+ "generated_files": 0,
+ "report_sha256": "e5d1bb92c30c3ba4e074a064ae3f968d80e350be9386ddde3ce50df2d80592f7"
+ },
+ "fixture_bytes_unchanged": true
+ },
+ {
+ "name": "ordinary_function_edit",
+ "before_source": "LIMIT = 100\n\n\ndef accepts(amount):\n return amount <= LIMIT\n",
+ "after_source": "LIMIT = 100\n\n\ndef accepts(amount):\n return amount < LIMIT\n",
+ "git_diff": "diff --git a/settings.py b/settings.py\nindex 5ce271c..1e0bfab 100644\n--- a/settings.py\n+++ b/settings.py\n@@ -2,4 +2,4 @@ LIMIT = 100\n \n \n def accepts(amount):\n- return amount <= LIMIT\n+ return amount < LIMIT\n",
+ "function": "accepts",
+ "argument": 100,
+ "before_result": true,
+ "after_result": false,
+ "target_count": 1,
+ "targets": [
+ {
+ "path": "settings.py",
+ "module": "settings",
+ "qualname": "accepts",
+ "lineno": 4,
+ "end_lineno": 5,
+ "changed_lines": [
+ 5
+ ],
+ "source": "def accepts(amount):\n return amount < LIMIT",
+ "is_method": false
+ }
+ ],
+ "fixture_bytes_unchanged": true
+ }
+ ],
+ "commands": [
+ {
+ "label": "module_constant:targets",
+ "arguments": [
+ "targets",
+ "--repo",
+ "/Users/me/hamon-testpilot-target-assessment-a219f250962c/current/fixture-module_constant-cv9pqad3/repo",
+ "--diff",
+ "/Users/me/hamon-testpilot-target-assessment-a219f250962c/current/fixture-module_constant-cv9pqad3/change.diff",
+ "--json"
+ ],
+ "returncode": 0,
+ "stdout": "{\n \"changed_functions\": []\n}\n",
+ "stderr": ""
+ },
+ {
+ "label": "module_constant:run",
+ "arguments": [
+ "run",
+ "--repo",
+ "/Users/me/hamon-testpilot-target-assessment-a219f250962c/current/fixture-module_constant-cv9pqad3/repo",
+ "--diff",
+ "/Users/me/hamon-testpilot-target-assessment-a219f250962c/current/fixture-module_constant-cv9pqad3/change.diff",
+ "--backend",
+ "scripted",
+ "--script",
+ "/Users/me/hamon-testpilot-target-assessment-a219f250962c/current/fixture-module_constant-cv9pqad3/script",
+ "--out",
+ "/Users/me/hamon-testpilot-target-assessment-a219f250962c/current/module_constant-run"
+ ],
+ "returncode": 1,
+ "stdout": "# TestPilot report: **no_changes**\n\ndiff touches no Python functions outside tests\n\n- Changed functions: none\n- Tests written: 0 in -\n- Repair rounds used: 0/3\n- Coverage: unavailable\n- Tokens: 0; cost: $0.000000\n\nwrote /Users/me/hamon-testpilot-target-assessment-a219f250962c/current/module_constant-run/testpilot.patch, /Users/me/hamon-testpilot-target-assessment-a219f250962c/current/module_constant-run/report.json, /Users/me/hamon-testpilot-target-assessment-a219f250962c/current/module_constant-run/report.md, /Users/me/hamon-testpilot-target-assessment-a219f250962c/current/module_constant-run/report.html\n",
+ "stderr": ""
+ },
+ {
+ "label": "module_constant:missing-explicit-targets",
+ "arguments": [
+ "targets",
+ "--repo",
+ "/Users/me/hamon-testpilot-target-assessment-a219f250962c/current/fixture-module_constant-cv9pqad3/repo",
+ "--diff",
+ "/Users/me/hamon-testpilot-target-assessment-a219f250962c/current/fixture-module_constant-cv9pqad3/change.diff",
+ "--target",
+ "settings.py::accepts"
+ ],
+ "returncode": 2,
+ "stdout": "",
+ "stderr": "usage: testpilot [-h] {run,targets} ...\ntestpilot: error: unrecognized arguments: --target settings.py::accepts\n"
+ },
+ {
+ "label": "module_constant:missing-explicit-run",
+ "arguments": [
+ "run",
+ "--repo",
+ "/Users/me/hamon-testpilot-target-assessment-a219f250962c/current/fixture-module_constant-cv9pqad3/repo",
+ "--diff",
+ "/Users/me/hamon-testpilot-target-assessment-a219f250962c/current/fixture-module_constant-cv9pqad3/change.diff",
+ "--target",
+ "settings.py::accepts",
+ "--backend",
+ "scripted",
+ "--script",
+ "/Users/me/hamon-testpilot-target-assessment-a219f250962c/current/fixture-module_constant-cv9pqad3/script",
+ "--out",
+ "/Users/me/hamon-testpilot-target-assessment-a219f250962c/current/fixture-module_constant-cv9pqad3/refused-explicit-output"
+ ],
+ "returncode": 2,
+ "stdout": "",
+ "stderr": "usage: testpilot [-h] {run,targets} ...\ntestpilot: error: unrecognized arguments: --target settings.py::accepts\n"
+ },
+ {
+ "label": "default_binding:targets",
+ "arguments": [
+ "targets",
+ "--repo",
+ "/Users/me/hamon-testpilot-target-assessment-a219f250962c/current/fixture-default_binding-p_zi87qq/repo",
+ "--diff",
+ "/Users/me/hamon-testpilot-target-assessment-a219f250962c/current/fixture-default_binding-p_zi87qq/change.diff",
+ "--json"
+ ],
+ "returncode": 0,
+ "stdout": "{\n \"changed_functions\": []\n}\n",
+ "stderr": ""
+ },
+ {
+ "label": "default_binding:run",
+ "arguments": [
+ "run",
+ "--repo",
+ "/Users/me/hamon-testpilot-target-assessment-a219f250962c/current/fixture-default_binding-p_zi87qq/repo",
+ "--diff",
+ "/Users/me/hamon-testpilot-target-assessment-a219f250962c/current/fixture-default_binding-p_zi87qq/change.diff",
+ "--backend",
+ "scripted",
+ "--script",
+ "/Users/me/hamon-testpilot-target-assessment-a219f250962c/current/fixture-default_binding-p_zi87qq/script",
+ "--out",
+ "/Users/me/hamon-testpilot-target-assessment-a219f250962c/current/default_binding-run"
+ ],
+ "returncode": 1,
+ "stdout": "# TestPilot report: **no_changes**\n\ndiff touches no Python functions outside tests\n\n- Changed functions: none\n- Tests written: 0 in -\n- Repair rounds used: 0/3\n- Coverage: unavailable\n- Tokens: 0; cost: $0.000000\n\nwrote /Users/me/hamon-testpilot-target-assessment-a219f250962c/current/default_binding-run/testpilot.patch, /Users/me/hamon-testpilot-target-assessment-a219f250962c/current/default_binding-run/report.json, /Users/me/hamon-testpilot-target-assessment-a219f250962c/current/default_binding-run/report.md, /Users/me/hamon-testpilot-target-assessment-a219f250962c/current/default_binding-run/report.html\n",
+ "stderr": ""
+ },
+ {
+ "label": "ordinary_function_edit:targets",
+ "arguments": [
+ "targets",
+ "--repo",
+ "/Users/me/hamon-testpilot-target-assessment-a219f250962c/current/fixture-ordinary_function_edit-8kc1vvey/repo",
+ "--diff",
+ "/Users/me/hamon-testpilot-target-assessment-a219f250962c/current/fixture-ordinary_function_edit-8kc1vvey/change.diff",
+ "--json"
+ ],
+ "returncode": 0,
+ "stdout": "{\n \"changed_functions\": [\n {\n \"path\": \"settings.py\",\n \"module\": \"settings\",\n \"qualname\": \"accepts\",\n \"lineno\": 4,\n \"end_lineno\": 5,\n \"changed_lines\": [\n 5\n ],\n \"source\": \"def accepts(amount):\\n return amount < LIMIT\",\n \"is_method\": false\n }\n ]\n}\n",
+ "stderr": ""
+ }
+ ],
+ "source_files_unchanged": 8,
+ "actual_cli_calls": 7,
+ "all_private_git_fixtures_removed": true
+}
diff --git a/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/assessment.log b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/assessment.log
new file mode 100644
index 0000000..20a8092
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/assessment.log
@@ -0,0 +1 @@
+{"cases": [{"name": "module_constant", "before": true, "after": false, "targets": 0, "run": {"exit": 1, "status": "no_changes", "message": "diff touches no Python functions outside tests", "model_calls": 0, "tokens": 0, "generated_files": 0, "report_sha256": "e5d1bb92c30c3ba4e074a064ae3f968d80e350be9386ddde3ce50df2d80592f7"}}, {"name": "default_binding", "before": 100, "after": 50, "targets": 0, "run": {"exit": 1, "status": "no_changes", "message": "diff touches no Python functions outside tests", "model_calls": 0, "tokens": 0, "generated_files": 0, "report_sha256": "e5d1bb92c30c3ba4e074a064ae3f968d80e350be9386ddde3ce50df2d80592f7"}}, {"name": "ordinary_function_edit", "before": true, "after": false, "targets": 1, "run": null}], "cli_calls": 7, "source_files_unchanged": 8, "python": "3.13.7 (v3.13.7:bcee1c32211, Aug 14 2025, 19:10:51) [Clang 16.0.0 (clang-1600.0.26.6)]", "receipt": "/Users/me/hamon-testpilot-target-assessment-a219f250962c/current/assessment-result.json"}
diff --git a/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/assessment.py b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/assessment.py
new file mode 100644
index 0000000..1be05bc
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/assessment.py
@@ -0,0 +1,71 @@
+import hashlib, json, os, subprocess, sys, tempfile
+from pathlib import Path
+root = Path.cwd()
+snapshot = root / "snapshot"
+receipt = {"source_commit": "e2285d68b2ea5eb2158c0a7e0d936ce5d56f8ff5", "python": sys.version, "qualification": "Exact-source read-only product assessment; disposable synthetic Git fixtures only", "cases": [], "commands": []}
+env = {k: v for k, v in os.environ.items() if not any(word in k.upper() for word in ("KEY", "TOKEN", "SECRET")) and not k.startswith("TESTPILOT_") and k != "PYTHONPATH"}
+env["PYTHONDONTWRITEBYTECODE"] = "1"
+def command(args, cwd, check=True):
+ p = subprocess.run(args, cwd=cwd, env=env, text=True, capture_output=True, timeout=15)
+ if check and p.returncode:
+ raise RuntimeError((args, p.returncode, p.stderr))
+ return p
+def call_cli(label, args):
+ p = command([sys.executable, "-B", "-m", "testpilot", *args], snapshot, False)
+ receipt["commands"].append({"label": label, "arguments": args, "returncode": p.returncode, "stdout": p.stdout, "stderr": p.stderr})
+ return p
+cases = [
+ ("module_constant", "LIMIT = 100\n\n\ndef accepts(amount):\n return amount <= LIMIT\n", "LIMIT = 50\n\n\ndef accepts(amount):\n return amount <= LIMIT\n", "accepts", 75, True, False, 0),
+ ("default_binding", "LIMIT: int = 100\n\n\ndef cap(amount, limit=LIMIT):\n return min(amount, limit)\n", "LIMIT: int = 50\n\n\ndef cap(amount, limit=LIMIT):\n return min(amount, limit)\n", "cap", 150, 100, 50, 0),
+ ("ordinary_function_edit", "LIMIT = 100\n\n\ndef accepts(amount):\n return amount <= LIMIT\n", "LIMIT = 100\n\n\ndef accepts(amount):\n return amount < LIMIT\n", "accepts", 100, True, False, 1),
+]
+for label, before, after, name, argument, expected_before, expected_after, expected_targets in cases:
+ with tempfile.TemporaryDirectory(prefix="fixture-" + label + "-", dir=root) as temp:
+ directory = Path(temp)
+ repo = directory / "repo"; repo.mkdir()
+ script = directory / "script"; script.mkdir()
+ (script / "01.txt").write_text("TRIPWIRE: a no_changes run must not consume this response.\n")
+ source = repo / "settings.py"
+ source.write_text(before)
+ command(["git", "init", "--quiet", str(repo)], directory)
+ command(["git", "-C", str(repo), "add", "--", "settings.py"], directory)
+ source.write_text(after)
+ diff = command(["git", "-C", str(repo), "diff", "--", "settings.py"], directory).stdout
+ diff_file = directory / "change.diff"; diff_file.write_text(diff)
+ old_namespace, new_namespace = {}, {}
+ exec(compile(before, label + ":before", "exec"), old_namespace)
+ exec(compile(after, label + ":after", "exec"), new_namespace)
+ old_result, new_result = old_namespace[name](argument), new_namespace[name](argument)
+ assert old_result == expected_before and new_result == expected_after and old_result != new_result
+ preview = call_cli(label + ":targets", ["targets", "--repo", str(repo), "--diff", str(diff_file), "--json"])
+ assert preview.returncode == 0, preview.stderr
+ targets = json.loads(preview.stdout)["changed_functions"]
+ assert len(targets) == expected_targets, targets
+ case = {"name": label, "before_source": before, "after_source": after, "git_diff": diff, "function": name, "argument": argument, "before_result": old_result, "after_result": new_result, "target_count": len(targets), "targets": targets}
+ if expected_targets == 0:
+ out = root / (label + "-run")
+ run = call_cli(label + ":run", ["run", "--repo", str(repo), "--diff", str(diff_file), "--backend", "scripted", "--script", str(script), "--out", str(out)])
+ report = json.loads((out / "report.json").read_text())
+ assert run.returncode == 1 and report["status"] == "no_changes"
+ assert report["changed_functions"] == [] and report["ledger"]["entries"] == [] and report["ledger"]["total_tokens"] == 0
+ assert report["test_files"] == {} and report["final"] is None and (out / "testpilot.patch").read_bytes() == b""
+ case["run"] = {"exit": run.returncode, "status": report["status"], "message": report["message"], "model_calls": len(report["ledger"]["entries"]), "tokens": report["ledger"]["total_tokens"], "generated_files": len(report["test_files"]), "report_sha256": hashlib.sha256((out / "report.json").read_bytes()).hexdigest()}
+ if label == "module_constant":
+ for mode in ["targets", "run"]:
+ requested = [mode, "--repo", str(repo), "--diff", str(diff_file), "--target", "settings.py::accepts"]
+ if mode == "run": requested += ["--backend", "scripted", "--script", str(script), "--out", str(directory / "refused-explicit-output")]
+ result = call_cli(label + ":missing-explicit-" + mode, requested)
+ assert result.returncode == 2 and "unrecognized arguments: --target settings.py::accepts" in result.stderr
+ assert not (directory / "refused-explicit-output").exists()
+ assert source.read_text() == after and diff_file.read_text() == diff
+ case["fixture_bytes_unchanged"] = True
+ receipt["cases"].append(case)
+manifest = json.loads((root / "source-manifest.json").read_text())
+for item in manifest["files"]:
+ data = (snapshot / item["path"]).read_bytes()
+ assert hashlib.sha256(data).hexdigest() == item["sha256"]
+receipt["source_files_unchanged"] = len(manifest["files"])
+receipt["actual_cli_calls"] = len(receipt["commands"])
+receipt["all_private_git_fixtures_removed"] = True
+(root / "assessment-result.json").write_text(json.dumps(receipt, indent=2) + "\n")
+print(json.dumps({"cases": [{"name": c["name"], "before": c["before_result"], "after": c["after_result"], "targets": c["target_count"], "run": c.get("run")} for c in receipt["cases"]], "cli_calls": receipt["actual_cli_calls"], "source_files_unchanged": receipt["source_files_unchanged"], "python": sys.version, "receipt": str(root / "assessment-result.json")}))
diff --git a/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/default_binding-run/report.html b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/default_binding-run/report.html
new file mode 100644
index 0000000..e4d101b
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/default_binding-run/report.html
@@ -0,0 +1,92 @@
+TestPilot run review · no_changes Skip to test results TestPilot / Run review
No selected changes no_changes No changed Python functions were selected for this run.
diff touches no Python functions outside tests
Both downloads are embedded in this file. Opening, reviewing or downloading does not run tests or apply the patch.
Generated cases passed / collected Unavailable
Repair rounds used / limit 0 / 3
Tokens · reported 0 Test results Generated files Repair history Plan and scope Coverage Model ledger Patch Final recorded result No generated-test result was recorded.
Final generated files Reported authored-test count: 0. This count is separate from verified generated-case execution above.
No file contents were recorded here.
Repair history Each round keeps its recorded source snapshot and result. A model response without a new run does not add an execution.
No generation or repair rounds were recorded.
Plan and selected source No model test plan was recorded.
No changed functions were selected.
Coverage Coverage unavailable. This report contains no comparable before/after measurement.
Model and token ledger No model calls were recorded.
Exact recorded patch The recorded patch is empty.
This file reviews the recorded TestPilot result. It does not rerun the repository, verify a model diagnosis, or change any source. Text is displayed literally; HTML-incompatible controls and surrogate characters use visible escapes. Embedded downloads retain the original artifact bytes.
Use your browser’s Print command to print the report. No scripts, remote resources, storage or account are required.
diff --git a/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/default_binding-run/report.json b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/default_binding-run/report.json
new file mode 100644
index 0000000..becfdcc
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/default_binding-run/report.json
@@ -0,0 +1,23 @@
+{
+ "status": "no_changes",
+ "changed_functions": [],
+ "repair_rounds_used": 0,
+ "max_repair_rounds": 3,
+ "test_files": {},
+ "tests_written": 0,
+ "final": null,
+ "patch": "",
+ "coverage": null,
+ "ledger": {
+ "total_tokens": 0,
+ "total_cost_usd": 0,
+ "cost_is_complete": true,
+ "tokens_estimated": false,
+ "by_model": {},
+ "entries": []
+ },
+ "rounds": [],
+ "plan": "",
+ "message": "diff touches no Python functions outside tests",
+ "ok": false
+}
\ No newline at end of file
diff --git a/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/default_binding-run/report.md b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/default_binding-run/report.md
new file mode 100644
index 0000000..f94cd8a
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/default_binding-run/report.md
@@ -0,0 +1,9 @@
+# TestPilot report: **no_changes**
+
+diff touches no Python functions outside tests
+
+- Changed functions: none
+- Tests written: 0 in -
+- Repair rounds used: 0/3
+- Coverage: unavailable
+- Tokens: 0; cost: $0.000000
diff --git a/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/default_binding-run/testpilot.patch b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/default_binding-run/testpilot.patch
new file mode 100644
index 0000000..e69de29
diff --git a/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/module_constant-run/report.html b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/module_constant-run/report.html
new file mode 100644
index 0000000..e4d101b
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/module_constant-run/report.html
@@ -0,0 +1,92 @@
+TestPilot run review · no_changes Skip to test results TestPilot / Run review
No selected changes no_changes No changed Python functions were selected for this run.
diff touches no Python functions outside tests
Both downloads are embedded in this file. Opening, reviewing or downloading does not run tests or apply the patch.
Generated cases passed / collected Unavailable
Repair rounds used / limit 0 / 3
Tokens · reported 0 Test results Generated files Repair history Plan and scope Coverage Model ledger Patch Final recorded result No generated-test result was recorded.
Final generated files Reported authored-test count: 0. This count is separate from verified generated-case execution above.
No file contents were recorded here.
Repair history Each round keeps its recorded source snapshot and result. A model response without a new run does not add an execution.
No generation or repair rounds were recorded.
Plan and selected source No model test plan was recorded.
No changed functions were selected.
Coverage Coverage unavailable. This report contains no comparable before/after measurement.
Model and token ledger No model calls were recorded.
Exact recorded patch The recorded patch is empty.
This file reviews the recorded TestPilot result. It does not rerun the repository, verify a model diagnosis, or change any source. Text is displayed literally; HTML-incompatible controls and surrogate characters use visible escapes. Embedded downloads retain the original artifact bytes.
Use your browser’s Print command to print the report. No scripts, remote resources, storage or account are required.
diff --git a/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/module_constant-run/report.json b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/module_constant-run/report.json
new file mode 100644
index 0000000..becfdcc
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/module_constant-run/report.json
@@ -0,0 +1,23 @@
+{
+ "status": "no_changes",
+ "changed_functions": [],
+ "repair_rounds_used": 0,
+ "max_repair_rounds": 3,
+ "test_files": {},
+ "tests_written": 0,
+ "final": null,
+ "patch": "",
+ "coverage": null,
+ "ledger": {
+ "total_tokens": 0,
+ "total_cost_usd": 0,
+ "cost_is_complete": true,
+ "tokens_estimated": false,
+ "by_model": {},
+ "entries": []
+ },
+ "rounds": [],
+ "plan": "",
+ "message": "diff touches no Python functions outside tests",
+ "ok": false
+}
\ No newline at end of file
diff --git a/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/module_constant-run/report.md b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/module_constant-run/report.md
new file mode 100644
index 0000000..f94cd8a
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/module_constant-run/report.md
@@ -0,0 +1,9 @@
+# TestPilot report: **no_changes**
+
+diff touches no Python functions outside tests
+
+- Changed functions: none
+- Tests written: 0 in -
+- Repair rounds used: 0/3
+- Coverage: unavailable
+- Tokens: 0; cost: $0.000000
diff --git a/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/module_constant-run/testpilot.patch b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/module_constant-run/testpilot.patch
new file mode 100644
index 0000000..e69de29
diff --git a/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/__init__.py b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/__init__.py
new file mode 100644
index 0000000..296210d
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/__init__.py
@@ -0,0 +1,7 @@
+"""TestPilot: diff-aware pytest generation with a run/repair loop."""
+__version__ = "0.1.0"
+
+from .diff import ChangedFunction, changed_functions, parse_unified_diff # noqa: F401
+from .loop import LoopResult, TestPilot # noqa: F401
+from .model import OpenAICompatClient, RoutingConfig, ScriptedModel, make_client # noqa: F401
+from .sandbox import SandboxResult, run_pytest # noqa: F401
diff --git a/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/__main__.py b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/__main__.py
new file mode 100644
index 0000000..6f149f5
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/__main__.py
@@ -0,0 +1,117 @@
+"""CLI for test generation and native diff-target inspection."""
+from __future__ import annotations
+
+import argparse
+import json
+import os
+import shutil
+import subprocess
+import sys
+from pathlib import Path
+
+from .diff import DiffSourceError, changed_functions
+from .loop import TestPilot, render_report, write_outputs
+from .model import ModelError, RoutingConfig, make_client
+
+
+def _python_executable(value: str) -> str:
+ executable = shutil.which(os.path.expanduser(value))
+ if executable is None:
+ raise argparse.ArgumentTypeError(f"Python interpreter not found or not executable: {value}")
+ # Resolve directory symlinks/.. before the sandbox changes cwd, but keep
+ # the final executable symlink so a venv does not become the base Python.
+ path = Path(executable)
+ return str(path.parent.resolve() / path.name)
+
+
+def _read_diff(args) -> str:
+ """Receive raw diff bytes without imposing a source-file encoding on a hunk."""
+ if args.diff == "-":
+ raw = getattr(sys.stdin, "buffer", sys.stdin).read()
+ elif args.diff:
+ raw = Path(args.diff).read_bytes()
+ else:
+ raw = subprocess.run(
+ ["git", "-C", args.repo, "diff", args.git_base, "--", "*.py"],
+ check=True, capture_output=True,
+ ).stdout
+ if isinstance(raw, str): # Direct callers may provide a text-only stream.
+ return raw
+ # Keep the former universal-newline behavior and preserve undecodable bytes.
+ # Selected Python source still uses its own encoding through tokenize.open.
+ text = raw.decode("utf-8", errors="surrogateescape")
+ return text.replace("\r\n", "\n").replace("\r", "\n")
+
+
+def _preview_targets(args) -> int:
+ """Print the native selector's result without starting generation or tests."""
+ try:
+ diff_text = _read_diff(args)
+ functions = changed_functions(args.repo, diff_text)
+ except (OSError, ValueError, SyntaxError, subprocess.CalledProcessError) as exc:
+ print(f"testpilot: cannot inspect targets: {exc}", file=sys.stderr)
+ return 2
+
+ if args.json:
+ print(json.dumps({"changed_functions": [f.to_dict() for f in functions]}, indent=2))
+ else:
+ noun = "function" if len(functions) == 1 else "functions"
+ print(f"{len(functions)} changed Python {noun} outside tests")
+ for function in functions:
+ path = json.dumps(function.path)
+ name = json.dumps(function.qualname)
+ print(f"{path}:{function.lineno}-{function.end_lineno} {name}")
+ return 0
+
+
+def main(argv: list[str] | None = None) -> int:
+ ap = argparse.ArgumentParser(prog="testpilot")
+ sub = ap.add_subparsers(dest="cmd", required=True)
+ r = sub.add_parser("run", help="generate, run and repair tests for a diff")
+ r.add_argument("--repo", required=True)
+ g = r.add_mutually_exclusive_group(required=True)
+ g.add_argument("--diff", help="unified diff file ('-' for stdin)")
+ g.add_argument("--git-base", help="diff the working tree against this git ref")
+ r.add_argument("--backend", choices=["scripted", "tokenfactory", "openai"], default=None,
+ help="default: $TESTPILOT_BACKEND or scripted")
+ r.add_argument("--script", help="dir of canned responses for the scripted backend")
+ r.add_argument("--base-url", help="override OpenAI-compatible base URL")
+ r.add_argument("--rounds", type=int, default=3, help="max repair rounds (default 3)")
+ r.add_argument("--timeout", type=float, default=60.0, help="pytest timeout per run, seconds")
+ r.add_argument("--python", type=_python_executable, metavar="EXECUTABLE",
+ help="project Python for all test runs; path or PATH command (default: this interpreter)")
+ r.add_argument("--max-tokens", type=int, default=None, help="total token budget for the run")
+ r.add_argument("--out", default="testpilot-out")
+
+ preview = sub.add_parser("targets", help="inspect changed functions without generating or running tests")
+ preview.add_argument("--repo", required=True)
+ source = preview.add_mutually_exclusive_group(required=True)
+ source.add_argument("--diff", help="unified diff file ('-' for stdin)")
+ source.add_argument("--git-base", help="diff the working tree against this git ref")
+ preview.add_argument("--json", action="store_true", help="include full native target records and source")
+ a = ap.parse_args(argv)
+
+ if a.cmd == "targets":
+ return _preview_targets(a)
+
+ diff_text = _read_diff(a)
+ try:
+ client = make_client(a.backend, script=a.script, base_url=a.base_url)
+ except ModelError as e:
+ print(f"testpilot: {e}", file=sys.stderr)
+ return 2
+ pilot = TestPilot(client, RoutingConfig.from_env(), max_repair_rounds=a.rounds, timeout_s=a.timeout,
+ max_total_tokens=a.max_tokens, python=a.python)
+ try:
+ res = pilot.run(a.repo, diff_text)
+ except DiffSourceError as e:
+ print(f"testpilot: invalid diff source: {e}", file=sys.stderr)
+ return 2
+ paths = write_outputs(res, a.out)
+ print(render_report(res).split("\n## Patch")[0])
+ print(f"wrote {', '.join(str(p) for p in paths.values())}")
+ return 0 if res.ok else 1
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/_pytest_generated.py b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/_pytest_generated.py
new file mode 100644
index 0000000..90b8272
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/_pytest_generated.py
@@ -0,0 +1,75 @@
+"""Run pytest with generated files added to its normal configured collection.
+
+Copied beside the sandbox manifest so the chosen project interpreter needs only
+pytest, not an installed copy of TestPilot. The project keeps its own collection
+roots, conftest files, fixtures, selection hooks and assertion rewriting.
+"""
+from __future__ import annotations
+
+import inspect
+import json
+import sys
+from pathlib import Path
+
+import pytest
+from _pytest.main import search_pypath
+
+if __name__ == "__main__":
+ # Match ``python -m pytest`` without adding the temporary runner directory
+ # to the project's import search path.
+ sys.path[0] = str(Path.cwd())
+
+
+class GeneratedFiles:
+ def __init__(self, files: dict[str, str]):
+ self.files = files
+ self.covered_paths: set[Path] = set()
+
+ @pytest.hookimpl(trylast=True)
+ def pytest_configure(self, config):
+ selected = set()
+ path_options = {}
+ if config.option.pyargs and "consider_namespace_packages" in inspect.signature(search_pypath).parameters:
+ path_options["consider_namespace_packages"] = config.getini("consider_namespace_packages")
+ for arg in config.args:
+ path = arg.split("::", 1)[0]
+ if config.option.pyargs:
+ path = search_pypath(path, **path_options) or path
+ selected.add(str(Path(path).resolve()))
+ roots = [Path(path) for path in selected if Path(path).is_dir()]
+ for path in self.files:
+ if path in selected:
+ continue
+ resolved = Path(path).resolve()
+ if any(resolved.is_relative_to(root) for root in roots):
+ self.covered_paths.add(resolved)
+ else:
+ config.args.append(path)
+
+ @pytest.hookimpl(tryfirst=True)
+ def pytest_collectstart(self, collector):
+ if isinstance(collector, pytest.Session) and self.covered_paths:
+ # pytest 8.0 skips a directory cached while resolving an explicit
+ # child; newer versions normalize that child out of initial paths.
+ # Traverse the normal root once, but retain the generated child's
+ # explicit treatment for python_files and ignored parent paths.
+ collector._initialpaths |= self.covered_paths
+ collector._initialpaths_with_parents |= {
+ parent for path in self.covered_paths for parent in (path, *path.parents)
+ }
+
+ @pytest.hookimpl(trylast=True)
+ def pytest_collection_modifyitems(self, items):
+ for item in items:
+ rel = self.files.get(str(item.path.resolve()))
+ if rel is not None:
+ item.user_properties.append(("testpilot.generated_file", rel))
+
+
+def main() -> int:
+ files = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
+ return pytest.main(sys.argv[2:], plugins=[GeneratedFiles(files)])
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/diff.py b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/diff.py
new file mode 100644
index 0000000..f92280a
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/diff.py
@@ -0,0 +1,258 @@
+"""Parse a unified diff and map changed lines onto Python functions (via ``ast``).
+
+Only the *new* side of the diff matters: TestPilot writes tests for the code as
+it exists after the change, so line numbers refer to the post-change file in the
+working tree.
+"""
+from __future__ import annotations
+
+import ast
+import os
+import re
+import tokenize
+from dataclasses import asdict, dataclass, field
+from pathlib import Path, PurePosixPath
+
+_HUNK = re.compile(r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@")
+
+
+@dataclass
+class FileChange:
+ """Changed lines for one file, in new-file line numbers."""
+
+ path: str
+ old_path: str | None
+ added_lines: set[int] = field(default_factory=set)
+ # added lines plus anchor lines around pure deletions
+ touched_lines: set[int] = field(default_factory=set)
+ is_new: bool = False
+ is_deleted: bool = False
+
+
+@dataclass
+class ChangedFunction:
+ path: str
+ module: str
+ qualname: str
+ lineno: int
+ end_lineno: int
+ changed_lines: list[int]
+ source: str
+ is_method: bool
+
+ @property
+ def import_name(self) -> str:
+ """Top-level name to import from ``module`` (class name for methods)."""
+ return self.qualname.split(".")[0]
+
+ def to_dict(self) -> dict:
+ return asdict(self)
+
+
+def _unquote_git_path(raw: str) -> str:
+ """Decode Git's C-quoted filename bytes, including octal UTF-8 sequences."""
+ escapes = {"a": 7, "b": 8, "t": 9, "n": 10, "v": 11, "f": 12, "r": 13,
+ '"': 34, "\\": 92}
+ decoded = bytearray()
+ index = 1 # Opening double quote.
+ while index < len(raw):
+ char = raw[index]
+ index += 1
+ if char == '"':
+ if index != len(raw) or 0 in decoded:
+ raise ValueError("invalid Git-quoted diff path")
+ return os.fsdecode(bytes(decoded))
+ if char != "\\":
+ decoded.extend(os.fsencode(char))
+ continue
+ if index >= len(raw):
+ break
+ escaped = raw[index]
+ index += 1
+ if escaped in escapes:
+ decoded.append(escapes[escaped])
+ elif (escaped in "0123" and index + 1 < len(raw)
+ and all(digit in "01234567" for digit in raw[index:index + 2])):
+ decoded.append(int(escaped + raw[index:index + 2], 8))
+ index += 2
+ else:
+ raise ValueError("invalid escape in Git-quoted diff path")
+ raise ValueError("unterminated Git-quoted diff path")
+
+
+def _strip_path(raw: str) -> str | None:
+ p = raw.split("\t")[0].strip()
+ if p.startswith('"'):
+ p = _unquote_git_path(p)
+ if p == "/dev/null":
+ return None
+ if p.startswith(("a/", "b/")):
+ p = p[2:]
+ return p
+
+
+def parse_unified_diff(text: str) -> list[FileChange]:
+ """Parse ``git diff`` / ``diff -u`` output into per-file changed line sets.
+
+ Hunk line counters are tracked so that removed lines whose content starts
+ with ``-- `` are not mistaken for file headers.
+ """
+ files: list[FileChange] = []
+ cur: FileChange | None = None
+ pending_old: str | None = None
+ old_left = new_left = 0
+ new_ln = 0
+ # Git hunk counts use LF-delimited records, not Unicode separators.
+ for line in text.split("\n"):
+ if cur is not None and (old_left > 0 or new_left > 0):
+ if line.startswith("\\"): # "\ No newline at end of file"
+ continue
+ if line.startswith("+"):
+ cur.added_lines.add(new_ln)
+ cur.touched_lines.add(new_ln)
+ new_ln += 1
+ new_left -= 1
+ elif line.startswith("-"):
+ # anchor a pure deletion to the surrounding new-file lines
+ for anchor in (new_ln - 1, new_ln):
+ if anchor >= 1:
+ cur.touched_lines.add(anchor)
+ old_left -= 1
+ else: # context line (" " prefix, or blank if whitespace was stripped)
+ new_ln += 1
+ new_left -= 1
+ old_left -= 1
+ continue
+ if line.startswith("diff --git "):
+ cur, pending_old = None, None
+ continue
+ if line.startswith("--- "):
+ pending_old = _strip_path(line[4:])
+ continue
+ if line.startswith("+++ "):
+ new_path = _strip_path(line[4:])
+ cur = FileChange(
+ path=new_path or pending_old or "",
+ old_path=pending_old,
+ is_new=pending_old is None,
+ is_deleted=new_path is None,
+ )
+ files.append(cur)
+ continue
+ m = _HUNK.match(line)
+ if m and cur is not None:
+ old_left = int(m.group(2)) if m.group(2) is not None else 1
+ new_left = int(m.group(4)) if m.group(4) is not None else 1
+ new_ln = int(m.group(3))
+ if new_left == 0: # pure deletion hunk: "+N,0" means "after line N"
+ new_ln += 1
+ return files
+
+
+def is_test_path(path: str) -> bool:
+ p = PurePosixPath(path)
+ return (
+ "tests" in p.parts
+ or "test" in p.parts
+ or p.name.startswith("test_")
+ or p.name.endswith("_test.py")
+ or p.name == "conftest.py"
+ )
+
+
+def module_name(path: str) -> str:
+ parts = list(PurePosixPath(path).with_suffix("").parts)
+ if parts and parts[0] in ("src", "lib"):
+ parts = parts[1:]
+ if parts and parts[-1] == "__init__":
+ parts = parts[:-1]
+ return ".".join(parts)
+
+
+def _iter_functions(body, prefix: str = "", in_class: bool = False):
+ """Yield (qualname, node, is_method) for top-level functions and methods.
+
+ Nested functions are attributed to their enclosing function. Functions
+ defined under ``if``/``try``/``with``/``match`` blocks at module/class level count.
+ """
+ for node in body:
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
+ yield prefix + node.name, node, in_class
+ elif isinstance(node, ast.ClassDef):
+ yield from _iter_functions(node.body, prefix + node.name + ".", True)
+ elif isinstance(node, ast.Match):
+ for case in node.cases:
+ yield from _iter_functions(case.body, prefix, in_class)
+ else:
+ for attr in ("body", "orelse", "finalbody"):
+ sub = getattr(node, attr, None)
+ if isinstance(sub, list):
+ yield from _iter_functions(sub, prefix, in_class)
+ for handler in getattr(node, "handlers", None) or []:
+ yield from _iter_functions(handler.body, prefix, in_class)
+
+
+def functions_touching(source: str, path: str, lines: set[int], added: set[int]) -> list[ChangedFunction]:
+ tree = ast.parse(source, filename=path)
+ # Python physical lines use LF/CRLF/CR; other separators can be literals.
+ src_lines = source.replace("\r\n", "\n").replace("\r", "\n").split("\n")
+ out: list[ChangedFunction] = []
+ for qualname, node, is_method in _iter_functions(tree.body):
+ start = min([node.lineno] + [d.lineno for d in node.decorator_list])
+ end = node.end_lineno or node.lineno
+ if not any(start <= ln <= end for ln in lines):
+ continue
+ out.append(
+ ChangedFunction(
+ path=path,
+ module=module_name(path),
+ qualname=qualname,
+ lineno=start,
+ end_lineno=end,
+ changed_lines=sorted(ln for ln in added if start <= ln <= end),
+ source="\n".join(src_lines[start - 1 : end]),
+ is_method=is_method,
+ )
+ )
+ return out
+
+
+class DiffSourceError(ValueError):
+ """A diff selects a source path outside the requested repository."""
+
+
+def _source_path(repo: Path, raw: str) -> Path:
+ """Resolve a relative diff path without admitting an outside source file."""
+ relative = Path(raw)
+ if relative.anchor or ".." in relative.parts:
+ raise DiffSourceError(f"diff source must be repository-relative: {raw!r}")
+ try:
+ resolved = (repo / relative).resolve()
+ except (OSError, RuntimeError, ValueError) as exc:
+ raise DiffSourceError(f"cannot resolve diff source: {raw!r}") from exc
+ if not resolved.is_relative_to(repo):
+ raise DiffSourceError(f"diff source resolves outside repository: {raw!r}")
+ return resolved
+
+
+def changed_functions(repo: str | Path, diff_text: str, include_tests: bool = False) -> list[ChangedFunction]:
+ """Return touched Python functions, rejecting selected sources outside ``repo``.
+
+ Paths must be relative without parent traversal. Symlinks are supported
+ when their resolved destination remains inside the resolved repository.
+ Source encoding follows Python's BOM and encoding-cookie rules.
+ """
+ repo = Path(repo).resolve()
+ result: list[ChangedFunction] = []
+ for fc in parse_unified_diff(diff_text):
+ if fc.is_deleted or not fc.path.endswith(".py"):
+ continue
+ if not include_tests and is_test_path(fc.path):
+ continue
+ f = _source_path(repo, fc.path)
+ if not f.is_file():
+ continue
+ with tokenize.open(f) as source_file:
+ source = source_file.read()
+ result.extend(functions_touching(source, fc.path, fc.touched_lines, fc.added_lines))
+ return result
diff --git a/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/html_report.py b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/html_report.py
new file mode 100644
index 0000000..bb30d90
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/html_report.py
@@ -0,0 +1,327 @@
+"""A self-contained, script-free review of the already serialized run artifacts.
+
+This is an output renderer, not a report importer, runner, or verifier. Embedded
+downloads retain the exact bytes supplied by write_outputs, including native
+newline handling. Visible text escapes controls/surrogates that HTML cannot
+represent faithfully; the JSON and patch downloads remain unchanged.
+"""
+from __future__ import annotations
+
+import base64
+import html
+import json
+import unicodedata
+
+
+_STYLE = """
+:root { color-scheme: light; --ink:#192d3b; --muted:#50616d; --line:#cbd5dc;
+ --paper:#fff; --wash:#f3f6f7; --accent:#155d68; --soft:#e8f2f1; }
+* { box-sizing:border-box; }
+html { scroll-padding-top:1.5rem; }
+body { margin:0; color:var(--ink); background:var(--wash);
+ font:16px/1.6 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; }
+a { color:var(--accent); text-underline-offset:.2em; }
+a:hover { text-decoration-thickness:2px; }
+:focus-visible { outline:3px solid #aa5e00; outline-offset:4px; }
+.skip { position:absolute; top:-100px; left:1rem; background:white; padding:.6rem; }
+.skip:focus { top:1rem; z-index:1; }
+.shell { width:min(1120px,100%); margin:auto; padding:2.5rem 2rem 3.5rem; }
+.eyebrow { margin:0 0 .7rem; color:var(--accent); font-size:.8rem;
+ font-weight:750; letter-spacing:.15em; text-transform:uppercase; }
+h1 { font-size:clamp(2rem,5vw,3.25rem); line-height:1.1; margin:.2rem 0 1rem; }
+h2 { font-size:1.4rem; line-height:1.3; margin:0 0 1rem; }
+h3 { font-size:1.08rem; margin:1.4rem 0 .7rem; }
+p { margin:.7rem 0; }
+.lead { font-size:1.1rem; max-width:78ch; }
+.muted,.note { color:var(--muted); }
+.note { font-size:.9rem; }
+.status { display:inline-block; padding:.18rem .7rem; margin-bottom:.5rem;
+ border:1px solid var(--line); border-radius:999px; font-weight:700; }
+.passed { background:#e7f3ed; color:#175137; }
+.failed,.error { background:#fceeed; color:#882e24; }
+.suspected_code_bug,.budget_exhausted,.model_error,.no_tests { background:#fff0d6; color:#6c4509; }
+.actions { display:flex; gap:.7rem; flex-wrap:wrap; margin:1.4rem 0 .6rem; }
+.button { display:inline-block; padding:.65rem 1rem; border:1px solid var(--accent);
+ border-radius:.35rem; background:var(--paper); font-weight:650; text-decoration:none; }
+.button.primary { background:var(--accent); color:white; }
+nav { display:flex; gap:.6rem 1.2rem; flex-wrap:wrap; padding:1rem 0;
+ border-top:1px solid var(--line); border-bottom:1px solid var(--line); margin:1.8rem 0; }
+.metrics { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:.8rem; margin:1.4rem 0; }
+.metric { background:var(--paper); border:1px solid var(--line); border-radius:.4rem; padding:1rem; }
+.metric dt { font-size:.85rem; color:var(--muted); }
+.metric dd { margin:0; font-weight:700; font-size:1.35rem; overflow-wrap:anywhere; }
+section { background:var(--paper); border:1px solid var(--line); border-radius:.5rem;
+ padding:1.5rem; margin:1.2rem 0; min-width:0; }
+.notice { border-left:4px solid var(--accent); background:var(--soft); padding:.7rem 1rem; }
+details { border:1px solid var(--line); border-radius:.35rem; margin:.65rem 0; min-width:0; }
+summary { padding:.75rem 1rem; cursor:pointer; font-weight:600; overflow-wrap:anywhere; }
+details[open] > summary { border-bottom:1px solid var(--line); background:var(--wash); }
+.detail-body { padding:.25rem 1rem .8rem; min-width:0; }
+.badge { display:inline-block; font-size:.76rem; margin-right:.4rem; padding:.05rem .4rem;
+ border:1px solid var(--line); border-radius:.2rem; font-weight:650; }
+code,pre { font-family:ui-monospace,SFMono-Regular,Consolas,"Liberation Mono",monospace; }
+code { overflow-wrap:anywhere; }
+pre { margin:.6rem 0; padding:1rem; border:1px solid var(--line); border-radius:.3rem;
+ background:#f7f9fa; line-height:1.5; font-size:.86rem; overflow:auto; max-width:100%;
+ tab-size:4; white-space:pre; }
+.prose { white-space:pre-wrap; overflow-wrap:anywhere; }
+.table-scroll { overflow-x:auto; max-width:100%; }
+table { width:100%; min-width:24rem; border-collapse:collapse; text-align:left; font-size:.9rem; }
+table.wide { min-width:46rem; }
+th,td { border-bottom:1px solid var(--line); padding:.6rem .7rem; vertical-align:top; overflow-wrap:anywhere; }
+th { color:var(--muted); font-weight:650; white-space:nowrap; }
+caption { text-align:left; padding:.3rem 0 .7rem; color:var(--muted); }
+.table-hint { display:none; }
+ol,ul { padding-left:1.4rem; }
+.cases { list-style:none; padding:0; }
+.case-label { color:var(--muted); font-size:.8rem; font-weight:400; }
+.rounds { padding-left:1.4rem; }
+.rounds > li { padding-left:.3rem; margin:1rem 0; }
+footer { margin-top:2rem; font-size:.9rem; color:var(--muted); }
+@media(max-width:620px) {
+ .shell { padding:1.5rem .8rem 2rem; }
+ section { padding:1rem; }
+ .metrics { grid-template-columns:1fr; gap:.5rem; }
+ .metric { display:flex; align-items:baseline; justify-content:space-between; gap:1rem; padding:.65rem .8rem; }
+ .metric dd { font-size:1.1rem; }
+ summary { padding:.65rem .7rem; }
+ .detail-body { padding:.2rem .7rem .6rem; }
+ .table-hint { display:block; margin:.3rem 0 .7rem; }
+}
+@media print {
+ body { background:white; font-size:10pt; }
+ .shell { width:100%; padding:0; }
+ nav,.actions,.skip,.download-note { display:none; }
+ section,details,.metric { border-color:#aaa; box-shadow:none; }
+ section { break-inside:auto; }
+ h2,h3,summary { break-after:avoid; }
+ pre { white-space:pre-wrap; overflow-wrap:anywhere; overflow:visible; }
+ .table-scroll { overflow:visible; }
+ table,table.wide { min-width:0; font-size:8pt; }
+ th { white-space:normal; }
+ .table-hint { display:none; }
+ details::details-content { content-visibility:visible; }
+ details > :not(summary) { display:block !important; }
+ .metrics { grid-template-columns:repeat(3,minmax(0,1fr)); }
+}
+"""
+
+_STATUS = {
+ "passed": ("Passed", "The runner reported a passing selected suite. Passing tests do not establish that the changed code is correct."),
+ "failed": ("Needs review", "The run did not qualify as passing. Inspect the recorded test results and diagnostics."),
+ "suspected_code_bug": ("Possible code bug", "The model flagged a possible code bug. Review its explanation and the retained failing tests."),
+ "no_changes": ("No selected changes", "No changed Python functions were selected for this run."),
+ "no_tests": ("No qualified generated tests", "No generated test case qualified as passing."),
+ "budget_exhausted": ("Token budget reached", "The run stopped at its configured token budget. Any earlier recorded results remain below."),
+ "model_error": ("Model request failed", "A model request failed. Any earlier recorded results remain below."),
+}
+
+
+def _text(value: object) -> str:
+ value = "—" if value is None else str(value)
+ visible = "".join(
+ ch.encode("unicode_escape").decode("ascii")
+ if (0xD800 <= ord(ch) <= 0xDFFF
+ or (unicodedata.category(ch) == "Cc" and ch not in "\n\t")) else ch
+ for ch in value
+ )
+ return html.escape(visible, quote=True)
+
+
+def _pre(value: object) -> str:
+ return '' + _text(value) + ' '
+
+
+def _disclosure(title: str, body: str, *, opened: bool = False) -> str:
+ return ('' + title
+ + ' ' + body + '
')
+
+
+def _table(headers: list[str], rows: list[list[object]], caption: str = "") -> str:
+ return ('Scroll the table horizontally to read all columns.
')
+
+
+def _files(files: dict[str, str]) -> str:
+ if not files:
+ return 'No file contents were recorded here.
'
+ return ''.join(_disclosure('' + _text(path) + '', _pre(content))
+ for path, content in sorted(files.items()))
+
+
+def _run(result: dict | None) -> str:
+ if result is None:
+ return 'No generated-test result was recorded.
'
+ parts = ['' + _text(result.get('summary')) + '
']
+ parts.append('Return code: ' + _text(result.get('returncode'))
+ + ' · Duration: ' + _text(result.get('duration_s')) + ' s'
+ + ' · Timeout: ' + ('yes' if result.get('timed_out') else 'no') + '
')
+ if not result.get('junit_available'):
+ parts.append('JUnit report unavailable. Recorded counts do not establish complete '
+ 'test execution; the authored-test count may be a static definition count.
')
+ parts.append(_table(['Passed', 'Failed', 'Errors', 'Skipped'],
+ [[result.get(k) for k in ('passed', 'failed', 'errors', 'skipped')]],
+ 'Recorded selected-suite case counts'))
+ generated = result.get('generated')
+ if generated is not None:
+ parts.append(_table(['Collected', 'Passed', 'Failed', 'Errors', 'Skipped'],
+ [[generated.get(k) for k in ('collected', 'passed', 'failed', 'error', 'skipped')]],
+ 'Recorded generated-case counts'))
+ else:
+ parts.append('Separate generated-case counts are unavailable.
')
+ cases = result.get('cases', [])
+ if cases:
+ generated_files = set(result.get('generated_files', []))
+ parts.append('Recorded test cases ')
+ for case in cases:
+ outcome = case.get('outcome', 'unknown')
+ style = outcome if outcome in ('passed', 'failed', 'error') else ''
+ label = 'Generated test' if case.get('generated_file') in generated_files else 'Other selected test'
+ title = ('' + _text(outcome) + ' '
+ '' + _text(case.get('nodeid')) + ' '
+ '' + label + ' ')
+ body = _pre(case['message']) if case.get('message') else 'No diagnostic message recorded.
'
+ parts.append('' + _disclosure(title, body, opened=outcome in ('failed', 'error')) + ' ')
+ parts.append(' ')
+ else:
+ parts.append('No individual test cases were recorded.
')
+ parts.append(_disclosure('Recorded pytest output',
+ 'The runner retains the last 3,000 characters in its report. '
+ 'This is the recorded tail, not a complete process log.
'
+ + _pre(result.get('output', ''))))
+ return ''.join(parts)
+
+
+def _rounds(rounds: list[dict]) -> str:
+ if not rounds:
+ return 'No generation or repair rounds were recorded.
'
+ parts = ['']
+ for record in rounds:
+ title = 'Round ' + _text(record.get('round')) + ' · ' + _text(record.get('kind'))
+ body = ''
+ if record.get('warnings'):
+ body += 'Recorded warnings ' + ''.join(
+ '' + _text(w) + ' ' for w in record['warnings']) + ' '
+ if record.get('contents'):
+ body += 'Tests at this round ' + _files(record['contents'])
+ else:
+ body += 'No new test-source snapshot was recorded for this round.
'
+ if record.get('files'):
+ body += 'Retained file names: ' + ', '.join('' + _text(p) + '' for p in record['files']) + '
'
+ if record.get('result') is None:
+ body += 'No test run was recorded for this round.
'
+ else:
+ if not record.get('contents'):
+ body += 'The preceding test result was retained; no new execution was recorded.
'
+ body += _run(record['result'])
+ parts.append('' + _disclosure(title, body) + ' ')
+ return ''.join(parts) + ' '
+
+
+def render_html_report(report_json: bytes, patch: bytes) -> str:
+ """Render the writer's current report JSON and exact patch as offline HTML.
+
+ The caller supplies the artifacts just written from the same LoopResult.
+ This function reads no paths, calls no model, and executes no source text.
+ """
+ report = json.loads(report_json)
+ status = report['status']
+ title, explanation = _STATUS.get(status, ('Recorded outcome', 'Inspect the recorded result below.'))
+ status_class = status if status in _STATUS else ''
+ final = report.get('final')
+ ledger = report['ledger']
+ token_note = 'estimated' if ledger['tokens_estimated'] else 'reported'
+ generated = final.get('generated') if final else None
+ verified_counts = bool(final and final.get('junit_available') and generated is not None)
+ generated_metric = (str(generated['passed']) + ' / ' + str(generated['collected'])) if verified_counts else 'Unavailable'
+ patch_uri = 'data:text/plain;charset=utf-8;base64,' + base64.b64encode(patch).decode('ascii')
+ json_uri = 'data:application/json;base64,' + base64.b64encode(report_json).decode('ascii')
+ parts = [' '
+ ' '
+ ' '
+ 'TestPilot run review · ' + _text(status) + ' '
+ 'Skip to test results '
+ 'TestPilot / Run review
' + _text(title) + ' '
+ '' + _text(status) + ' '
+ '' + explanation + '
']
+ if report.get('message'):
+ parts.append('' + _text(report['message']) + '
')
+ parts.append(''
+ 'Both downloads are embedded in this file. Opening, reviewing or '
+ 'downloading does not run tests or apply the patch.
')
+ parts.append('
Generated cases passed / collected '
+ + _text(generated_metric) + '
Repair rounds used / limit '
+ + _text(report['repair_rounds_used']) + ' / ' + _text(report['max_repair_rounds'])
+ + '
Tokens · ' + token_note + ' '
+ + _text(ledger['total_tokens']) + ' ')
+ parts.append('' + ''.join(
+ '' + name + ' ' for key, name in
+ [('result', 'Test results'), ('tests', 'Generated files'), ('rounds', 'Repair history'),
+ ('scope', 'Plan and scope'), ('coverage', 'Coverage'), ('ledger', 'Model ledger'), ('patch', 'Patch')]) + ' ')
+ parts.append('Final recorded result '
+ + _run(final) + ' ')
+ parts.append('Final generated files '
+ 'Reported authored-test count: ' + _text(report['tests_written'])
+ + '. This count is separate from verified generated-case execution above.
'
+ + _files(report['test_files']) + ' ')
+ parts.append('Repair history '
+ 'Each round keeps its recorded source snapshot and result. '
+ 'A model response without a new run does not add an execution.
'
+ + _rounds(report['rounds']) + ' ')
+ parts.append('Plan and selected source ')
+ if report.get('plan'):
+ parts.append('Model test plan ' + _pre(report['plan']))
+ else:
+ parts.append('No model test plan was recorded.
')
+ for fn in report['changed_functions']:
+ heading = ('' + _text(fn['path']) + '::' + _text(fn['qualname']) + ' '
+ 'lines ' + _text(fn.get('lineno')) + '–' + _text(fn.get('end_lineno')) + ' ')
+ parts.append(_disclosure(heading, _pre(fn.get('source', ''))))
+ if not report['changed_functions']:
+ parts.append('No changed functions were selected.
')
+ parts.append('Coverage ')
+ coverage = report.get('coverage')
+ if coverage is None:
+ parts.append('Coverage unavailable. This report contains no comparable before/after measurement.
')
+ else:
+ parts.append(_table(['Measurement', 'Before', 'After'], [
+ ['Total coverage (%)', coverage.get('total_before'), coverage.get('total_after')],
+ ['Changed executable lines (%)', coverage.get('changed_lines_before'), coverage.get('changed_lines_after')]],
+ 'Recorded coverage comparison'))
+ parts.append('Total change: ' + _text(coverage.get('total_delta'))
+ + ' percentage points · Changed executable lines: ' + _text(coverage.get('changed_lines_executable'))
+ + '. Coverage measures executed lines, not assertion quality.
')
+ parts.append('Model and token ledger ')
+ if not ledger.get('entries'):
+ parts.append('No model calls were recorded.
')
+ else:
+ parts.append('Token counts are ' + ('estimated from text length.' if ledger['tokens_estimated']
+ else 'reported by the configured client.') + '
')
+ if ledger['cost_is_complete']:
+ parts.append('Cost at supplied prices: $' + _text(ledger['total_cost_usd']) + ' USD.
')
+ else:
+ parts.append('Cost is unpriced or incomplete. The recorded subtotal ($'
+ + _text(ledger['total_cost_usd']) + ' USD) does not establish the full cost.
')
+ parts.append(_table(['Call', 'Role', 'Model', 'Prompt tokens', 'Completion tokens', 'Token source', 'Recorded cost (USD)'],
+ [[i + 1, e['role'], e['model'], e['prompt_tokens'], e['completion_tokens'],
+ 'estimated' if e['estimated'] else 'reported', e['cost_usd']]
+ for i, e in enumerate(ledger['entries'])], 'Calls in recorded order'))
+ parts.append('Exact recorded patch ')
+ if report['patch']:
+ parts.append(_pre(report['patch']))
+ else:
+ parts.append('The recorded patch is empty.
')
+ parts.append('This file reviews the recorded TestPilot result. It does not rerun '
+ 'the repository, verify a model diagnosis, or change any source. Text is displayed literally; '
+ 'HTML-incompatible controls and surrogate characters use visible escapes. Embedded downloads '
+ 'retain the original artifact bytes.
Use your browser’s Print command to print the report. '
+ 'No scripts, remote resources, storage or account are required.
\n')
+ return ''.join(parts)
diff --git a/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/loop.py b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/loop.py
new file mode 100644
index 0000000..3057a9c
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/loop.py
@@ -0,0 +1,528 @@
+"""generate -> run -> repair loop with a round limit and a token/cost ledger.
+
+Flow for one diff:
+
+1. ``diff.changed_functions`` finds changed Python functions.
+2. Baseline: run the repo's existing tests in the sandbox (coverage before).
+3. Planner model (Nemotron, large) writes a short test plan.
+4. Editor model (small/fast) turns plan + sources into pytest file(s).
+5. Sandbox runs existing + generated tests. Green -> done.
+6. Otherwise the editor model gets the failure report and rewrites the tests,
+ up to ``max_repair_rounds`` times. It may instead answer
+ ``VERDICT: CODE_BUG`` when the failure looks like a real bug in the code;
+ the loop then stops rather than bending tests to fit broken code.
+7. Emit a ``git apply``-able patch adding the tests, plus a coverage delta.
+"""
+from __future__ import annotations
+
+import difflib
+import json
+import re
+from dataclasses import asdict, dataclass, field
+from pathlib import Path, PurePosixPath
+
+from .diff import ChangedFunction, changed_functions
+from .html_report import render_html_report
+from .model import ChatClient, ModelError, RoutingConfig, Usage
+from .sandbox import IGNORE, SandboxResult, run_pytest
+
+SYSTEM_PLANNER = (
+ "You are TestPilot's planner. Given changed Python functions from a pull request, "
+ "list the behaviours that pytest tests should pin down: normal cases, edge cases, "
+ "error cases. Be concise. Do not write code."
+)
+SYSTEM_EDITOR = (
+ "You are TestPilot's test writer. Write pytest tests for the changed functions. "
+ "Import the code under test by its module path. Tests must be deterministic, fast, "
+ "offline, and must not sleep or touch the network. Output each file as a fenced block "
+ "whose info string is `python path=tests/test_.py`. Add new test files; do not "
+ "replace the repository's existing tests. Output only test files."
+)
+SYSTEM_REPAIR = (
+ "You are TestPilot's test repairer. The tests below failed. If the TEST is wrong "
+ "(bad expectation, import, fixture, timing), output the corrected files in the same "
+ "fenced format, using the current generated file paths. Omitted files are kept unchanged; "
+ "output only the generated files that need repair. Existing repository tests cannot be "
+ "changed by this workflow. If the failure shows a genuine bug in the CODE under test, do not "
+ "weaken the test: reply with a line `VERDICT: CODE_BUG` followed by a one-paragraph "
+ "explanation."
+)
+
+# Closers start a line; keep legacy adjacent close/open fences compatible.
+_FENCE = re.compile(
+ r"```([^\n`]*)\n(.*?)^[ \t]*(?:`{3,}[ \t]*(?:\r?\n|$)|```(?=```[^\n`]*\n))",
+ re.S | re.M,
+)
+_PATH_IN_INFO = re.compile(r"path\s*=\s*([^\s`]+)")
+_PATH_COMMENT = re.compile(r"^#\s*(?:file|path)\s*:\s*(\S+)\s*\n", re.I)
+_VALID_TEST_PATH = re.compile(r"^tests/(?:[A-Za-z0-9_]+/)*test_[A-Za-z0-9_]+\.py$")
+DEFAULT_TEST_PATH = "tests/test_testpilot_generated.py"
+
+
+class BudgetExceeded(RuntimeError):
+ pass
+
+
+# ---------------------------------------------------------------------- ledger
+@dataclass
+class LedgerEntry:
+ role: str
+ model: str
+ prompt_tokens: int
+ completion_tokens: int
+ estimated: bool
+ cost_usd: float
+
+
+@dataclass
+class Ledger:
+ routing: RoutingConfig
+ max_total_tokens: int | None = None
+ entries: list[LedgerEntry] = field(default_factory=list)
+
+ def record(self, role: str, model: str, usage: Usage) -> LedgerEntry:
+ e = LedgerEntry(role, model, usage.prompt_tokens, usage.completion_tokens, usage.estimated,
+ round(self.routing.cost(model, usage), 8))
+ self.entries.append(e)
+ return e
+
+ @property
+ def total_tokens(self) -> int:
+ return sum(e.prompt_tokens + e.completion_tokens for e in self.entries)
+
+ @property
+ def total_cost_usd(self) -> float:
+ return round(sum(e.cost_usd for e in self.entries), 8)
+
+ def check_budget(self) -> None:
+ if self.max_total_tokens is not None and self.total_tokens >= self.max_total_tokens:
+ raise BudgetExceeded(f"token budget {self.max_total_tokens} reached ({self.total_tokens})")
+
+ def by_model(self) -> dict[str, dict]:
+ out: dict[str, dict] = {}
+ for e in self.entries:
+ m = out.setdefault(e.model, {"calls": 0, "prompt_tokens": 0, "completion_tokens": 0, "cost_usd": 0.0})
+ m["calls"] += 1
+ m["prompt_tokens"] += e.prompt_tokens
+ m["completion_tokens"] += e.completion_tokens
+ m["cost_usd"] = round(m["cost_usd"] + e.cost_usd, 8)
+ return out
+
+ def to_dict(self) -> dict:
+ priced = all(self.routing.has_price(e.model) for e in self.entries)
+ return {
+ "total_tokens": self.total_tokens,
+ "total_cost_usd": self.total_cost_usd,
+ "cost_is_complete": priced,
+ "tokens_estimated": any(e.estimated for e in self.entries),
+ "by_model": self.by_model(),
+ "entries": [asdict(e) for e in self.entries],
+ }
+
+
+# --------------------------------------------------------------- output parsing
+def sanitize_test_path(path: str | None) -> tuple[str, str | None]:
+ """Force generated files into ``tests/**/test_*.py``. Returns (path, warning)."""
+ if path:
+ p = PurePosixPath(path.strip().lstrip("./"))
+ cand = p.as_posix()
+ if ".." not in p.parts and _VALID_TEST_PATH.match(cand):
+ return cand, None
+ name = re.sub(r"[^A-Za-z0-9_]", "_", p.stem) or "generated"
+ if not name.startswith("test_"):
+ name = "test_" + name
+ return f"tests/{name}.py", f"rewrote unsafe/invalid test path {path!r} -> tests/{name}.py"
+ return DEFAULT_TEST_PATH, None
+
+
+def parse_test_files(text: str) -> tuple[dict[str, str], list[str]]:
+ """Extract ``{path: content}`` from fenced code blocks in a model reply."""
+ files: dict[str, str] = {}
+ warnings: list[str] = []
+ for info, body in _FENCE.findall(text):
+ lang = info.strip().split(" ")[0].lower() if info.strip() else ""
+ if lang not in ("", "python", "py"):
+ continue
+ m = _PATH_IN_INFO.search(info)
+ path = m.group(1) if m else None
+ if path is None:
+ cm = _PATH_COMMENT.match(body)
+ if cm:
+ path, body = cm.group(1), body[cm.end():]
+ path, w = sanitize_test_path(path)
+ if w:
+ warnings.append(w)
+ if path in files:
+ warnings.append(f"duplicate block for {path}; later block wins")
+ files[path] = body if body.endswith("\n") else body + "\n"
+ return files, warnings
+
+
+def parse_verdict(text: str) -> str | None:
+ m = re.search(r"VERDICT:\s*CODE_BUG\s*(.*)", text, re.S)
+ if m is None:
+ return None
+ return m.group(1).strip() or "model reported a code bug"
+
+
+class _GeneratedPathConflict(ValueError):
+ pass
+
+
+def _path_occupied(repo: Path, rel: str) -> bool:
+ """A new regular file must not replace a path or traverse a symlink."""
+ path = repo
+ parts = PurePosixPath(rel).parts
+ for i, part in enumerate(parts):
+ path = path / part
+ if path.is_symlink() or (path.exists() and (i == len(parts) - 1 or not path.is_dir())):
+ return True
+ return False
+
+
+def _require_additions(repo: Path, files: dict[str, str]) -> None:
+ for rel in files:
+ if _path_occupied(repo, rel):
+ raise _GeneratedPathConflict(f"cannot add generated tests without replacing repository path {rel!r}")
+
+
+def _pytest_module_name(repo: Path, rel: str) -> str:
+ """Use pytest's default regular-package identity within the copied project."""
+ path = repo / rel
+ names = [] if path.name == "__init__.py" else [path.stem]
+ parent = path.parent
+ while (parent != repo.parent and parent.name.isidentifier()
+ and (parent / "__init__.py").is_file()):
+ names.insert(0, parent.name)
+ parent = parent.parent
+ return ".".join(names)
+
+
+def _repository_module_names(repo: Path) -> set[str]:
+ """Reserve copied Python modules without walking ignored or symlinked dirs."""
+ modules: set[str] = set()
+ for root, dirs, files in repo.walk(follow_symlinks=False):
+ ignored = IGNORE(str(root), dirs + files)
+ dirs[:] = [name for name in dirs if name not in ignored]
+ for name in files:
+ if name.endswith(".py") and name not in ignored:
+ modules.add(_pytest_module_name(repo, (root / name).relative_to(repo).as_posix()))
+ return modules
+
+
+def _merge_test_files(repo: Path, current: dict[str, str], incoming: dict[str, str],
+ aliases: dict[str, str]) -> tuple[dict[str, str], list[str]]:
+ """Apply a partial repair, reserving repository paths and pytest module names.
+
+ Keep aliases for renamed suggestions so a repair using either the original
+ suggestion or the displayed generated path updates the same generated file.
+ """
+ merged, remapped = dict(current), dict(aliases)
+ reserved = set(current) | set(incoming) | set(aliases.values())
+ reserved_modules = {_pytest_module_name(repo, rel) for rel in reserved}
+ occupied_modules = _repository_module_names(repo)
+ occupied_modules.update(_pytest_module_name(repo, rel) for rel in current)
+ updates: dict[str, str] = {}
+ warnings: list[str] = []
+ for rel, content in incoming.items():
+ target = aliases.get(rel, rel)
+ path_conflict = target not in current and _path_occupied(repo, target)
+ module_conflict = (target not in current
+ and _pytest_module_name(repo, target) in occupied_modules)
+ if path_conflict or module_conflict:
+ test_root = repo / "tests"
+ if test_root.is_symlink() or (test_root.exists() and not test_root.is_dir()):
+ raise _GeneratedPathConflict("cannot add generated tests: repository 'tests' is not a regular directory")
+ # Path conflicts retain the safe root fallback; import-name-only
+ # conflicts keep local fixtures and relative imports in their scope.
+ parent = PurePosixPath("tests") if path_conflict else PurePosixPath(target).parent
+ stem = PurePosixPath(rel).stem
+ suffix = 1
+ while True:
+ tag = "" if suffix == 1 else f"_{suffix}"
+ target = str(parent / f"{stem}_testpilot{tag}.py")
+ module = _pytest_module_name(repo, target)
+ if (target not in reserved and not _path_occupied(repo, target)
+ and module not in occupied_modules and module not in reserved_modules):
+ break
+ suffix += 1
+ if target != rel:
+ warnings.append(f"generated suggestion {rel!r} uses {target!r}; kept repository paths and pytest module names")
+ if target in updates and updates[target] != content:
+ raise _GeneratedPathConflict(f"conflicting repair blocks resolve to generated path {target!r}; kept previous tests")
+ updates[target] = content
+ remapped[rel] = target
+ reserved.add(target)
+ module = _pytest_module_name(repo, target)
+ reserved_modules.add(module)
+ occupied_modules.add(module)
+ merged[target] = content
+ _require_additions(repo, merged)
+ aliases.update(remapped)
+ return merged, warnings
+
+
+# ----------------------------------------------------------------- patch/coverage
+def make_patch(repo: Path, files: dict[str, str], *, additions_only: bool = False) -> str:
+ """Unified diff; generated patches use additions only so git refuses collisions."""
+ chunks = []
+ for rel in sorted(files):
+ # Git counts LF-delimited lines; other separators remain file content.
+ new = re.findall(r"[^\n]*\n|[^\n]+$", files[rel])
+ target = repo / rel
+ if not additions_only and target.exists():
+ old = re.findall(r"[^\n]*\n|[^\n]+$", target.read_text(encoding="utf-8"))
+ header = [f"diff --git a/{rel} b/{rel}\n"]
+ body = difflib.unified_diff(old, new, f"a/{rel}", f"b/{rel}")
+ else:
+ old = []
+ header = [f"diff --git a/{rel} b/{rel}\n", "new file mode 100644\n"]
+ body = difflib.unified_diff(old, new, "/dev/null", f"b/{rel}")
+ lines = list(body)
+ if lines:
+ chunks.append("".join(header + lines))
+ return "".join(chunks)
+
+
+def _changed_line_coverage(res: SandboxResult | None, funcs: list[ChangedFunction]) -> tuple[int, int] | None:
+ if res is None or res.coverage is None:
+ return None
+ hit = total = 0
+ for f in funcs:
+ executable = res.coverage.executable(f.path)
+ executed = res.coverage.executed(f.path)
+ for ln in f.changed_lines:
+ if ln in executable:
+ total += 1
+ hit += ln in executed
+ return hit, total
+
+
+def coverage_delta(before: SandboxResult | None, after: SandboxResult | None,
+ funcs: list[ChangedFunction]) -> dict | None:
+ if before is None or after is None or before.coverage is None or after.coverage is None:
+ return None
+ b, a = _changed_line_coverage(before, funcs), _changed_line_coverage(after, funcs)
+
+ def pct(t):
+ return round(100.0 * t[0] / t[1], 1) if t and t[1] else None
+
+ return {
+ "total_before": round(before.coverage.percent_total, 1),
+ "total_after": round(after.coverage.percent_total, 1),
+ "total_delta": round(after.coverage.percent_total - before.coverage.percent_total, 1),
+ "changed_lines_executable": b[1] if b else 0,
+ "changed_lines_before": pct(b),
+ "changed_lines_after": pct(a),
+ }
+
+
+# --------------------------------------------------------------------- prompts
+def _functions_block(funcs: list[ChangedFunction]) -> str:
+ parts = []
+ for f in funcs:
+ imp = f"from {f.module} import {f.import_name}" if f.module else f"import {f.import_name}"
+ parts.append(f"### {f.path}::{f.qualname} (lines {f.lineno}-{f.end_lineno}; import with `{imp}`)\n"
+ f"```python\n{f.source}\n```")
+ return "\n\n".join(parts)
+
+
+def _files_block(files: dict[str, str]) -> str:
+ return "\n\n".join(f"```python path={p}\n{c}```" for p, c in sorted(files.items()))
+
+
+# ------------------------------------------------------------------------ loop
+@dataclass
+class RoundRecord:
+ round: int
+ kind: str # generate | repair
+ files: list[str]
+ result: dict | None
+ warnings: list[str] = field(default_factory=list)
+ contents: dict[str, str] = field(default_factory=dict)
+
+
+@dataclass
+class LoopResult:
+ status: str # passed | failed | suspected_code_bug | no_changes | no_tests | budget_exhausted | model_error
+ changed_functions: list[dict]
+ repair_rounds_used: int
+ max_repair_rounds: int
+ test_files: dict[str, str]
+ tests_written: int
+ final: dict | None
+ patch: str
+ coverage: dict | None
+ ledger: dict
+ rounds: list[RoundRecord]
+ plan: str = ""
+ message: str = ""
+
+ @property
+ def ok(self) -> bool:
+ return self.status == "passed"
+
+ def to_dict(self) -> dict:
+ d = asdict(self)
+ d["ok"] = self.ok
+ return d
+
+
+def count_tests(files: dict[str, str], result: SandboxResult | None, existing_ids: set[str]) -> int:
+ """Number of generated test cases (from junit when available, else static count)."""
+ if result is not None and result.generated_files and result.junit_available:
+ return len(result.generated_cases)
+ if result is not None and result.cases:
+ return sum(1 for c in result.cases if c.nodeid not in existing_ids)
+ return sum(len(re.findall(r"^\s*(?:async\s+)?def test_", c, re.M)) for c in files.values())
+
+
+class TestPilot:
+ __test__ = False # not a pytest class
+
+ def __init__(self, client: ChatClient, routing: RoutingConfig | None = None, *, max_repair_rounds: int = 3,
+ timeout_s: float = 60.0, max_total_tokens: int | None = None, coverage: bool | None = None,
+ python: str | None = None):
+ self.client = client
+ self.routing = routing or RoutingConfig()
+ self.max_repair_rounds = max_repair_rounds
+ self.timeout_s = timeout_s
+ self.max_total_tokens = max_total_tokens
+ self.coverage = coverage
+ self.python = python
+
+ def _ask(self, ledger: Ledger, role: str, system: str, user: str) -> str:
+ ledger.check_budget()
+ model = self.routing.model_for(role)
+ resp = self.client.chat(model, [{"role": "system", "content": system}, {"role": "user", "content": user}])
+ ledger.record(role, model, resp.usage)
+ return resp.content
+
+ def _run(self, repo: Path, files: dict[str, str] | None) -> SandboxResult:
+ return run_pytest(repo, files, timeout=self.timeout_s, coverage=self.coverage, python=self.python)
+
+ def run(self, repo: str | Path, diff_text: str) -> LoopResult:
+ repo = Path(repo)
+ ledger = Ledger(self.routing, self.max_total_tokens)
+ funcs = changed_functions(repo, diff_text)
+ rounds: list[RoundRecord] = []
+ files: dict[str, str] = {}
+ aliases: dict[str, str] = {}
+ plan = ""
+ final: SandboxResult | None = None
+ repairs = 0
+ status, message = "failed", ""
+
+ def result(st: str, msg: str = "", baseline: SandboxResult | None = None) -> LoopResult:
+ existing = {c.nodeid for c in baseline.cases} if baseline else set()
+ patch = ""
+ try:
+ _require_additions(repo, files)
+ patch = make_patch(repo, files, additions_only=True)
+ except _GeneratedPathConflict as e:
+ st, msg = "failed", str(e)
+ return LoopResult(
+ status=st, changed_functions=[f.to_dict() for f in funcs], repair_rounds_used=repairs,
+ max_repair_rounds=self.max_repair_rounds, test_files=files,
+ tests_written=count_tests(files, final, existing) if files else 0,
+ final=final.to_dict() if final else None,
+ patch=patch,
+ coverage=coverage_delta(baseline, final, funcs), ledger=ledger.to_dict(), rounds=rounds,
+ plan=plan, message=msg)
+
+ if not funcs:
+ return result("no_changes", "diff touches no Python functions outside tests")
+
+ baseline = self._run(repo, None)
+ try:
+ plan = self._ask(ledger, "planner", SYSTEM_PLANNER,
+ f"Changed functions:\n\n{_functions_block(funcs)}\n\nWrite the test plan.")
+ reply = self._ask(ledger, "editor", SYSTEM_EDITOR,
+ f"Test plan:\n{plan}\n\nChanged functions:\n\n{_functions_block(funcs)}\n\n"
+ "Write the pytest file(s).")
+ proposed, warns = parse_test_files(reply)
+ if not proposed:
+ rounds.append(RoundRecord(0, "generate", [], None, warns + ["no python code block in reply"]))
+ return result("no_tests", "model produced no test files", baseline)
+ files, path_warnings = _merge_test_files(repo, files, proposed, aliases)
+ warns.extend(path_warnings)
+ final = self._run(repo, files)
+ rounds.append(RoundRecord(0, "generate", sorted(files), final.to_dict(), warns, dict(files)))
+
+ while not final.ok and repairs < self.max_repair_rounds:
+ repairs += 1
+ reply = self._ask(ledger, "repair", SYSTEM_REPAIR,
+ f"Changed functions:\n\n{_functions_block(funcs)}\n\nCurrent tests:\n\n"
+ f"{_files_block(files)}\n\nFailure report:\n{final.failure_report()}")
+ verdict = parse_verdict(reply)
+ if verdict:
+ rounds.append(RoundRecord(repairs, "repair", sorted(files), None, []))
+ return result("suspected_code_bug", verdict, baseline)
+ new_files, warns = parse_test_files(reply)
+ if not new_files:
+ warns.append("repair reply had no code block; keeping previous tests")
+ rounds.append(RoundRecord(repairs, "repair", sorted(files), final.to_dict(), warns))
+ continue
+ files, path_warnings = _merge_test_files(repo, files, new_files, aliases)
+ warns.extend(path_warnings)
+ final = self._run(repo, files)
+ rounds.append(RoundRecord(repairs, "repair", sorted(files), final.to_dict(), warns, dict(files)))
+ if final.ok:
+ status = "passed"
+ elif (final.generated_files and not final.timed_out and final.returncode in (0, 5)
+ and not final.failed and not final.errors):
+ status = "no_tests"
+ message = ("no generated test case passed; existing-suite passes do not qualify "
+ f"the generated patch: {final.summary()}")
+ else:
+ status, message = "failed", f"still failing after {repairs} repair round(s): {final.summary()}"
+ except BudgetExceeded as e:
+ status, message = "budget_exhausted", str(e)
+ except ModelError as e:
+ status, message = "model_error", str(e)
+ except _GeneratedPathConflict as e:
+ status, message = "failed", str(e)
+ rounds.append(RoundRecord(repairs, "repair" if repairs else "generate", sorted(files),
+ None, [message], dict(files)))
+ return result(status, message, baseline)
+
+
+# ---------------------------------------------------------------------- report
+def render_report(res: LoopResult) -> str:
+ """Render UTF-8 text, visibly escaping unencodable filesystem/string surrogates."""
+ lines = [f"# TestPilot report: **{res.status}**", ""]
+ if res.message:
+ lines += [res.message, ""]
+ lines.append(f"- Changed functions: {', '.join(f['path'] + '::' + f['qualname'] for f in res.changed_functions) or 'none'}")
+ lines.append(f"- Tests written: {res.tests_written} in {', '.join(sorted(res.test_files)) or '-'}")
+ lines.append(f"- Repair rounds used: {res.repair_rounds_used}/{res.max_repair_rounds}")
+ if res.final:
+ lines.append(f"- Final run: {res.final['summary']} in {res.final['duration_s']}s")
+ c = res.coverage
+ if c:
+ lines.append(f"- Coverage (total): {c['total_before']}% -> {c['total_after']}% ({c['total_delta']:+} pts)")
+ lines.append(f"- Coverage (changed lines, n={c['changed_lines_executable']}): "
+ f"{c['changed_lines_before']}% -> {c['changed_lines_after']}%")
+ else:
+ lines.append("- Coverage: unavailable")
+ lg = res.ledger
+ cost = f"${lg['total_cost_usd']:.6f}" if lg["cost_is_complete"] else "unpriced (set TESTPILOT_PRICES)"
+ lines.append(f"- Tokens: {lg['total_tokens']}{' (estimated)' if lg['tokens_estimated'] else ''}; cost: {cost}")
+ for m, s in lg["by_model"].items():
+ lines.append(f" - `{m}`: {s['calls']} calls, {s['prompt_tokens']} in / {s['completion_tokens']} out")
+ if res.patch:
+ lines += ["", "## Patch", "", "```diff", res.patch.rstrip("\n"), "```"]
+ text = "\n".join(lines) + "\n"
+ return text.encode("utf-8", errors="backslashreplace").decode("utf-8")
+
+
+def write_outputs(res: LoopResult, out_dir: str | Path) -> dict[str, Path]:
+ out = Path(out_dir)
+ out.mkdir(parents=True, exist_ok=True)
+ paths = {"patch": out / "testpilot.patch", "json": out / "report.json", "md": out / "report.md",
+ "html": out / "report.html"}
+ paths["patch"].write_text(res.patch, encoding="utf-8")
+ paths["json"].write_text(json.dumps(res.to_dict(), indent=2), encoding="utf-8")
+ paths["md"].write_text(render_report(res), encoding="utf-8")
+ paths["html"].write_text(render_html_report(paths["json"].read_bytes(), paths["patch"].read_bytes()), encoding="utf-8")
+ return paths
diff --git a/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/model.py b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/model.py
new file mode 100644
index 0000000..6d4abca
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/model.py
@@ -0,0 +1,241 @@
+"""OpenAI-compatible chat client interface, a deterministic scripted stub, and
+planner/editor model routing.
+
+Nebius Token Factory exposes an OpenAI-compatible API. Per the Token Factory
+quickstart (https://docs.tokenfactory.nebius.com/quickstart, retrieved
+2026-10-04) the client is configured with::
+
+ base_url="https://api.tokenfactory.nebius.com/v1/"
+ api_key=os.environ.get("NEBIUS_API_KEY")
+
+and requests go to ``POST {base_url}chat/completions``. Swapping the scripted
+stub for Token Factory is therefore a backend + base_url + key change (see
+:func:`make_client`).
+
+Default model IDs are NVIDIA Nemotron models named in the Token Factory docs
+(https://docs.tokenfactory.nebius.com/august-2026-deprecation-notice lists
+``nvidia/nemotron-3-super-120b-a12b`` and ``nvidia/Nemotron-3_5-Lightning`` as
+current replacement models). Confirm both in the live model catalog before use;
+override via TESTPILOT_PLANNER_MODEL / TESTPILOT_EDITOR_MODEL.
+"""
+from __future__ import annotations
+
+import json
+import os
+import time
+import urllib.error
+import urllib.request
+from dataclasses import dataclass, field
+from pathlib import Path
+from typing import Callable, Protocol
+
+TOKEN_FACTORY_BASE_URL = "https://api.tokenfactory.nebius.com/v1/"
+TOKEN_FACTORY_DOCS = "https://docs.tokenfactory.nebius.com/quickstart"
+DEFAULT_PLANNER_MODEL = "nvidia/nemotron-3-super-120b-a12b"
+DEFAULT_EDITOR_MODEL = "nvidia/Nemotron-3_5-Lightning"
+
+
+class ModelError(RuntimeError):
+ pass
+
+
+class ScriptExhausted(ModelError):
+ pass
+
+
+@dataclass
+class Usage:
+ prompt_tokens: int
+ completion_tokens: int
+ estimated: bool = False
+
+ @property
+ def total_tokens(self) -> int:
+ return self.prompt_tokens + self.completion_tokens
+
+
+@dataclass
+class ChatResponse:
+ content: str
+ model: str
+ usage: Usage
+ raw: dict | None = None
+
+
+class ChatClient(Protocol):
+ def chat(self, model: str, messages: list[dict], *, temperature: float = 0.2,
+ max_tokens: int | None = None) -> ChatResponse: ...
+
+
+def estimate_tokens(text: str) -> int:
+ """Rough chars/4 token estimate; used only when no API usage is available."""
+ return max(1, (len(text) + 3) // 4)
+
+
+def _messages_text(messages: list[dict]) -> str:
+ return "\n".join(str(m.get("content", "")) for m in messages)
+
+
+# --------------------------------------------------------------------------- stub
+class ScriptedModel:
+ """Deterministic stand-in for an LLM: replays canned responses in order.
+
+ Every call is recorded in ``self.calls`` (model, messages) so tests can
+ assert on routing and prompts. Token usage is a deterministic estimate.
+ """
+
+ def __init__(self, responses: list[str] | Callable[[str, list[dict]], str], name: str = "scripted"):
+ self._responses = responses if callable(responses) else list(responses)
+ self.name = name
+ self.calls: list[tuple[str, list[dict]]] = []
+
+ @classmethod
+ def from_dir(cls, path: str | Path) -> "ScriptedModel":
+ files = sorted(p for p in Path(path).iterdir() if p.suffix in (".md", ".txt") and p.is_file())
+ if not files:
+ raise ModelError(f"no *.md/*.txt responses in script dir {path}")
+ return cls([p.read_text(encoding="utf-8") for p in files], name=f"scripted:{Path(path).name}")
+
+ def chat(self, model: str, messages: list[dict], *, temperature: float = 0.2,
+ max_tokens: int | None = None) -> ChatResponse:
+ self.calls.append((model, messages))
+ if callable(self._responses):
+ content = self._responses(model, messages)
+ else:
+ if not self._responses:
+ raise ScriptExhausted(f"{self.name}: script exhausted after {len(self.calls) - 1} calls")
+ content = self._responses.pop(0)
+ usage = Usage(estimate_tokens(_messages_text(messages)), estimate_tokens(content), estimated=True)
+ return ChatResponse(content=content, model=model, usage=usage)
+
+
+# --------------------------------------------------------------------- real client
+Transport = Callable[[str, dict, bytes, float], dict]
+
+
+def _urllib_transport(url: str, headers: dict, body: bytes, timeout: float) -> dict:
+ req = urllib.request.Request(url, data=body, headers=headers, method="POST")
+ with urllib.request.urlopen(req, timeout=timeout) as resp: # noqa: S310 (https URL from config)
+ return json.loads(resp.read().decode("utf-8"))
+
+
+class OpenAICompatClient:
+ """Minimal stdlib client for any OpenAI-compatible ``/chat/completions`` API."""
+
+ RETRY_STATUS = {429, 500, 502, 503, 504}
+
+ def __init__(self, base_url: str = TOKEN_FACTORY_BASE_URL, api_key: str | None = None, *,
+ timeout: float = 120.0, retries: int = 2, transport: Transport | None = None,
+ sleep: Callable[[float], None] = time.sleep):
+ if not api_key:
+ raise ModelError("API key missing (set NEBIUS_API_KEY)")
+ self.base_url = base_url.rstrip("/") + "/"
+ self._api_key = api_key
+ self.timeout = timeout
+ self.retries = retries
+ self._transport = transport or _urllib_transport
+ self._sleep = sleep
+
+ def chat(self, model: str, messages: list[dict], *, temperature: float = 0.2,
+ max_tokens: int | None = None) -> ChatResponse:
+ payload: dict = {"model": model, "messages": messages, "temperature": temperature}
+ if max_tokens is not None:
+ payload["max_tokens"] = max_tokens
+ headers = {"Content-Type": "application/json", "Accept": "application/json",
+ "Authorization": f"Bearer {self._api_key}"}
+ url = self.base_url + "chat/completions"
+ body = json.dumps(payload).encode("utf-8")
+ attempt = 0
+ while True:
+ try:
+ data = self._transport(url, headers, body, self.timeout)
+ break
+ except urllib.error.HTTPError as e:
+ if e.code in self.RETRY_STATUS and attempt < self.retries:
+ attempt += 1
+ self._sleep(2 ** attempt)
+ continue
+ try:
+ detail = e.read().decode("utf-8", "replace")[:500] if hasattr(e, "read") else ""
+ except TimeoutError:
+ # Preserve a terminal status even if its diagnostic body stalls.
+ detail = "response body timed out"
+ finally:
+ e.close()
+ raise ModelError(f"HTTP {e.code} from {url}: {detail}") from e
+ except (urllib.error.URLError, TimeoutError) as e:
+ # urllib can raise TimeoutError directly while reading a response body.
+ if attempt < self.retries:
+ attempt += 1
+ self._sleep(2 ** attempt)
+ continue
+ reason = e.reason if isinstance(e, urllib.error.URLError) else str(e)
+ raise ModelError(f"connection error to {url}: {reason}") from e
+ try:
+ content = data["choices"][0]["message"]["content"] or ""
+ except (KeyError, IndexError, TypeError) as e:
+ raise ModelError(f"unexpected response shape: {str(data)[:300]}") from e
+ u = data.get("usage") or {}
+ if "prompt_tokens" in u:
+ usage = Usage(int(u.get("prompt_tokens", 0)), int(u.get("completion_tokens", 0)))
+ else:
+ usage = Usage(estimate_tokens(_messages_text(messages)), estimate_tokens(content), estimated=True)
+ return ChatResponse(content=content, model=data.get("model", model), usage=usage, raw=data)
+
+
+# ------------------------------------------------------------------------ routing
+@dataclass
+class RoutingConfig:
+ """Which model does which job, plus per-model prices for the cost ledger.
+
+ ``prices`` maps model id -> (USD per 1M input tokens, USD per 1M output
+ tokens). It is empty by default: fill it from the Token Factory pricing page
+ (or TESTPILOT_PRICES='{"model": [in, out]}') — TestPilot does not guess.
+ """
+
+ planner_model: str = DEFAULT_PLANNER_MODEL
+ editor_model: str = DEFAULT_EDITOR_MODEL
+ prices: dict[str, tuple[float, float]] = field(default_factory=dict)
+
+ @classmethod
+ def from_env(cls, env: dict | None = None) -> "RoutingConfig":
+ env = os.environ if env is None else env
+ prices = {k: (float(v[0]), float(v[1])) for k, v in json.loads(env.get("TESTPILOT_PRICES", "{}")).items()}
+ return cls(
+ planner_model=env.get("TESTPILOT_PLANNER_MODEL", DEFAULT_PLANNER_MODEL),
+ editor_model=env.get("TESTPILOT_EDITOR_MODEL", DEFAULT_EDITOR_MODEL),
+ prices=prices,
+ )
+
+ def model_for(self, role: str) -> str:
+ if role == "planner":
+ return self.planner_model
+ if role in ("editor", "repair"):
+ return self.editor_model
+ raise ValueError(f"unknown role {role!r}")
+
+ def cost(self, model: str, usage: Usage) -> float:
+ pin, pout = self.prices.get(model, (0.0, 0.0))
+ return usage.prompt_tokens * pin / 1e6 + usage.completion_tokens * pout / 1e6
+
+ def has_price(self, model: str) -> bool:
+ return model in self.prices
+
+
+def make_client(backend: str | None = None, *, script: str | Path | None = None,
+ base_url: str | None = None, api_key: str | None = None,
+ env: dict | None = None) -> ChatClient:
+ """Build a client. ``backend`` is ``scripted`` (default) or ``tokenfactory``/``openai``."""
+ env = os.environ if env is None else env
+ backend = (backend or env.get("TESTPILOT_BACKEND") or "scripted").lower()
+ if backend == "scripted":
+ script = script or env.get("TESTPILOT_SCRIPT")
+ if not script:
+ raise ModelError("scripted backend needs --script DIR (or TESTPILOT_SCRIPT)")
+ return ScriptedModel.from_dir(script)
+ if backend in ("tokenfactory", "openai"):
+ return OpenAICompatClient(
+ base_url=base_url or env.get("TESTPILOT_BASE_URL") or TOKEN_FACTORY_BASE_URL,
+ api_key=api_key or env.get("NEBIUS_API_KEY"),
+ )
+ raise ModelError(f"unknown backend {backend!r}")
diff --git a/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/sandbox.py b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/sandbox.py
new file mode 100644
index 0000000..dd6b25f
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/snapshot/testpilot/sandbox.py
@@ -0,0 +1,273 @@
+"""Run pytest against a throwaway copy of a repo, with a wall-clock timeout.
+
+Isolation is process-level only: a temp copy of the repo, a fresh process
+group killed on timeout, and an environment with credential-looking variables
+removed. It is NOT a security boundary against hostile code — run TestPilot in
+a container/VM for untrusted repos.
+"""
+from __future__ import annotations
+
+import json
+import os
+import shutil
+import signal
+import subprocess
+import sys
+import tempfile
+import time
+import xml.etree.ElementTree as ET
+from dataclasses import asdict, dataclass, field
+from functools import lru_cache
+from pathlib import Path, PurePosixPath
+
+IGNORE = shutil.ignore_patterns(".git", "__pycache__", ".pytest_cache", ".venv", "venv",
+ "*.pyc", ".coverage", "node_modules", ".mypy_cache")
+_SECRET_MARKERS = ("KEY", "TOKEN", "SECRET", "PASSWORD", "CREDENTIAL")
+_OMIT = "*/tests/*,*/test_*.py,*_test.py,*/conftest.py"
+
+
+@dataclass
+class CaseResult:
+ nodeid: str
+ outcome: str # passed | failed | error | skipped
+ message: str = ""
+ generated_file: str | None = None
+
+
+@dataclass
+class CoverageReport:
+ percent_total: float
+ files: dict[str, dict[str, list[int]]] = field(default_factory=dict)
+
+ def executed(self, path: str) -> set[int]:
+ return set(self.files.get(path, {}).get("executed", []))
+
+ def executable(self, path: str) -> set[int]:
+ f = self.files.get(path, {})
+ return set(f.get("executed", [])) | set(f.get("missing", []))
+
+
+@dataclass
+class SandboxResult:
+ returncode: int | None
+ timed_out: bool
+ duration_s: float
+ timeout_s: float
+ output: str
+ cases: list[CaseResult] = field(default_factory=list)
+ coverage: CoverageReport | None = None
+ generated_files: list[str] = field(default_factory=list)
+ junit_available: bool = False
+
+ def _count(self, outcome: str) -> int:
+ return sum(1 for c in self.cases if c.outcome == outcome)
+
+ @property
+ def passed(self) -> int:
+ return self._count("passed")
+
+ @property
+ def failed(self) -> int:
+ return self._count("failed")
+
+ @property
+ def errors(self) -> int:
+ return self._count("error")
+
+ @property
+ def skipped(self) -> int:
+ return self._count("skipped")
+
+ @property
+ def generated_cases(self) -> list[CaseResult]:
+ return [c for c in self.cases if c.generated_file in self.generated_files]
+
+ def generated_counts(self) -> dict[str, int] | None:
+ if not self.generated_files:
+ return None
+ cases = self.generated_cases
+ return {"collected": len(cases),
+ **{outcome: sum(c.outcome == outcome for c in cases)
+ for outcome in ("passed", "failed", "error", "skipped")}}
+
+ @property
+ def ok(self) -> bool:
+ return (not self.timed_out and self.returncode == 0 and self.failed == 0
+ and self.errors == 0 and self.passed > 0
+ and (not self.generated_files
+ or any(c.outcome == "passed" for c in self.generated_cases)))
+
+ def summary(self) -> str:
+ if self.timed_out:
+ return f"TIMEOUT after {self.timeout_s:g}s"
+ text = (f"{self.passed} passed, {self.failed} failed, {self.errors} errors, "
+ f"{self.skipped} skipped (rc={self.returncode})")
+ generated = self.generated_counts()
+ if generated is not None:
+ text += (f"; generated: {generated['passed']} passed of "
+ f"{generated['collected']} collected")
+ return text
+
+ def failure_report(self, limit: int = 4000) -> str:
+ parts = [self.summary()]
+ generated = self.generated_counts()
+ if generated is not None and generated["passed"] == 0:
+ parts.append("No generated test case passed. Existing-suite passes cannot verify "
+ "the generated files. Write runnable tests and check project collection "
+ "rules, deselection and skip conditions.")
+ if self.timed_out:
+ parts.append("The test run exceeded the time limit and was killed. "
+ "Look for infinite loops, blocking I/O, or very slow tests.")
+ for c in self.cases:
+ if c.outcome in ("failed", "error"):
+ parts.append(f"--- {c.outcome.upper()}: {c.nodeid}\n{c.message.strip()}")
+ if not any(c.outcome in ("failed", "error") for c in self.cases):
+ parts.append("--- pytest output (tail)\n" + self.output[-2000:])
+ text = "\n".join(parts)
+ return text if len(text) <= limit else text[: limit - 20] + "\n...[truncated]"
+
+ def to_dict(self) -> dict:
+ d = asdict(self)
+ d.update(passed=self.passed, failed=self.failed, errors=self.errors,
+ skipped=self.skipped, ok=self.ok, summary=self.summary())
+ d["generated"] = self.generated_counts()
+ d["output"] = self.output[-3000:]
+ return d
+
+
+@lru_cache(maxsize=None)
+def coverage_available(python: str) -> bool:
+ return subprocess.run([python, "-c", "import coverage"], capture_output=True).returncode == 0
+
+
+def clean_env(root: Path) -> dict[str, str]:
+ env = {k: v for k, v in os.environ.items() if not any(m in k.upper() for m in _SECRET_MARKERS)}
+ for k in ("PYTEST_ADDOPTS", "PYTEST_PLUGINS", "COVERAGE_PROCESS_START", "COVERAGE_FILE"):
+ env.pop(k, None)
+ paths = [str(root / "src")] if (root / "src").is_dir() else []
+ env.update(PYTHONPATH=os.pathsep.join(paths + [str(root)]), PYTHONDONTWRITEBYTECODE="1", PYTHONHASHSEED="0")
+ return env
+
+
+def _run(cmd: list[str], cwd: Path, env: dict, timeout: float) -> tuple[int | None, str, bool]:
+ """Collect output, with at most one extra second of POSIX timeout cleanup."""
+ proc = subprocess.Popen(cmd, cwd=cwd, env=env, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT, text=True, errors="replace", start_new_session=True)
+ try:
+ out, _ = proc.communicate(timeout=timeout)
+ return proc.returncode, out, False
+ except subprocess.TimeoutExpired:
+ try:
+ os.killpg(proc.pid, signal.SIGKILL)
+ except (ProcessLookupError, AttributeError, PermissionError):
+ proc.kill()
+ try:
+ # A detached helper may retain stdout after the target group dies.
+ # POSIX communicate uses synchronous reads that we can stop and close;
+ # preserve the existing Windows reader-thread cleanup behavior.
+ out, _ = proc.communicate(timeout=1.0 if os.name == "posix" else None)
+ except subprocess.TimeoutExpired as exc:
+ # TimeoutExpired.output is bytes even in text mode. Preserve complete
+ # diagnostics plus a replacement for a character cut off by the kill.
+ out = (exc.output or b"").decode(proc.stdout.encoding, errors="replace")
+ out = out.replace("\r\n", "\n").replace("\r", "\n")
+ proc.stdout.close()
+ proc.poll() # Reap the killed leader without waiting for escaped helpers.
+ return None, out or "", True
+
+
+def parse_junit(path: Path) -> list[CaseResult]:
+ root = ET.parse(path).getroot()
+ cases: list[CaseResult] = []
+ for tc in root.iter("testcase"):
+ cls, name = tc.get("classname", ""), tc.get("name", "")
+ nodeid = f"{cls}::{name}" if cls else name
+ outcome, msg = "passed", ""
+ for tag, label in (("failure", "failed"), ("error", "error"), ("skipped", "skipped")):
+ el = tc.find(tag)
+ if el is not None:
+ outcome = label
+ msg = (el.get("message") or "") + ("\n" + el.text if el.text else "")
+ break
+ generated_file = None
+ for prop in tc.findall("./properties/property"):
+ if prop.get("name") == "testpilot.generated_file":
+ generated_file = prop.get("value")
+ break
+ cases.append(CaseResult(nodeid, outcome, msg, generated_file))
+ return cases
+
+
+def _parse_coverage(path: Path, work: Path) -> CoverageReport:
+ data = json.loads(path.read_text())
+ files = {}
+ resolved = work.resolve()
+ for name, info in data.get("files", {}).items():
+ p = Path(name)
+ if p.is_absolute():
+ try:
+ name = p.resolve().relative_to(resolved).as_posix()
+ except ValueError:
+ continue
+ files[PurePosixPath(name).as_posix()] = {"executed": info.get("executed_lines", []),
+ "missing": info.get("missing_lines", [])}
+ return CoverageReport(float(data.get("totals", {}).get("percent_covered", 0.0)), files)
+
+
+def _safe_join(root: Path, rel: str) -> Path:
+ p = (root / rel).resolve()
+ if root.resolve() not in p.parents:
+ raise ValueError(f"refusing to write outside sandbox: {rel}")
+ return p
+
+
+def run_pytest(repo: str | Path, extra_files: dict[str, str] | None = None, *, timeout: float = 60.0,
+ python: str | None = None, coverage: bool | None = None,
+ pytest_args: tuple[str, ...] = ()) -> SandboxResult:
+ """Copy ``repo`` to a temp dir, add ``extra_files``, run pytest, collect results."""
+ python = python or sys.executable
+ use_cov = coverage_available(python) if coverage is None else coverage
+ with tempfile.TemporaryDirectory(prefix="testpilot-") as td:
+ tdp = Path(td)
+ work = tdp / "repo"
+ shutil.copytree(repo, work, ignore=IGNORE)
+ for rel, content in (extra_files or {}).items():
+ dest = _safe_join(work, rel)
+ dest.parent.mkdir(parents=True, exist_ok=True)
+ dest.write_text(content, encoding="utf-8")
+ junit = tdp / "junit.xml"
+ options = ["-q", "-p", "no:cacheprovider", "--rootdir", str(work), "-o", "addopts=",
+ "-o", "junit_family=xunit2", f"--junitxml={junit}", *pytest_args]
+ generated_files = sorted(extra_files or {})
+ pt = ["-m", "pytest", *options]
+ if generated_files:
+ runner = tdp / "generated_pytest.py"
+ manifest = tdp / "generated_files.json"
+ shutil.copyfile(Path(__file__).with_name("_pytest_generated.py"), runner)
+ manifest.write_text(json.dumps({str(_safe_join(work, rel)): rel for rel in generated_files}),
+ encoding="utf-8")
+ pt = [str(runner), str(manifest), *options]
+ data_file = tdp / ".coverage"
+ if use_cov:
+ cmd = [python, "-m", "coverage", "run", f"--data-file={data_file}", f"--source={work}",
+ f"--omit={_OMIT}", *pt]
+ else:
+ cmd = [python, *pt]
+ env = clean_env(work)
+ t0 = time.monotonic()
+ rc, out, timed_out = _run(cmd, work, env, timeout)
+ dur = time.monotonic() - t0
+ junit_available = junit.exists() and not timed_out
+ cases = parse_junit(junit) if junit_available else []
+ cov = None
+ if use_cov and not timed_out:
+ # Reserve the report destination after pytest so a failed report
+ # command cannot reuse a file left by the test run.
+ with tempfile.TemporaryDirectory(prefix="coverage-report-", dir=tdp) as report_dir:
+ cov_json = Path(report_dir) / "cov.json"
+ crc, _, _ = _run([python, "-m", "coverage", "json", f"--data-file={data_file}", "-o", str(cov_json), "-q"],
+ work, env, 60)
+ # Exit 2 can mean a complete report is below configured fail_under.
+ cov = _parse_coverage(cov_json, work) if crc in (0, 2) and cov_json.exists() else None
+ return SandboxResult(rc, timed_out, round(dur, 3), timeout, out, cases, cov,
+ generated_files, junit_available)
diff --git a/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/source-manifest.json b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/source-manifest.json
new file mode 100644
index 0000000..73a6ee4
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/baseline-assessment/source-manifest.json
@@ -0,0 +1,54 @@
+{
+ "source_commit": "e2285d68b2ea5eb2158c0a7e0d936ce5d56f8ff5",
+ "qualification": "Current-main eight-module assessment; no source edits or dependencies installed",
+ "files": [
+ {
+ "path": "testpilot/__main__.py",
+ "bytes": 5275,
+ "git_blob": "6f149f5484cf7369f4f9569170cd450279c40994",
+ "sha256": "83caf8df097902120a0cfbd6adb897d5b6be7ba2407b0217606e7038599b5a5f"
+ },
+ {
+ "path": "testpilot/diff.py",
+ "bytes": 9396,
+ "git_blob": "f92280a9ad3f9f79713dce7ba7ae28c55414783a",
+ "sha256": "659021d4a4b462b86eadc25957407b683c733fec60ae33c0703295cb7640a06c"
+ },
+ {
+ "path": "testpilot/loop.py",
+ "bytes": 24064,
+ "git_blob": "3057a9c7c9777a23e73f8b56979670001eb512fd",
+ "sha256": "990d6df8b65877e6f25e319771ccad189767575587b4a165f3c3a8e2280c4c93"
+ },
+ {
+ "path": "testpilot/__init__.py",
+ "bytes": 390,
+ "git_blob": "296210dd0d58703de4a4e4c9ad72a0944c9400ca",
+ "sha256": "65c63b80d7da34a83a6049a79c24b8335878cd49cc79951fdefc5f1532ae841f"
+ },
+ {
+ "path": "testpilot/model.py",
+ "bytes": 10175,
+ "git_blob": "6d4abca38cc1c256df00bcac7923e2dc0bd42b16",
+ "sha256": "f6ae697f1f5d686ffdcb96f5451d1a9600d7d41c7bcf324d7e8c276a107e8673"
+ },
+ {
+ "path": "testpilot/sandbox.py",
+ "bytes": 11877,
+ "git_blob": "dd6b25f1a93bc6f313ae8c8d01ede5ba83440b5b",
+ "sha256": "2e852248ffc238371cf2b11b5797a88c519330db359b2cb969bc61aa72ef1931"
+ },
+ {
+ "path": "testpilot/html_report.py",
+ "bytes": 20656,
+ "git_blob": "bb30d903542d836f5afe8e116ba894f93d9d3a0c",
+ "sha256": "183703d05f32552f7f7c39aa958c39bae61d824e3fa9cd6a6dd686f0d4c228a4"
+ },
+ {
+ "path": "testpilot/_pytest_generated.py",
+ "bytes": 2943,
+ "git_blob": "90b82725bc0eb1ff3d7cb517c4c9dedcfd14bdcc",
+ "sha256": "d65352d7bade965d7b9dc73efe5d4d01844fa8c7474a1bdcdb3b708f6decf768"
+ }
+ ]
+}
diff --git a/docs/receiving/explicit-targets-a219f250962c/checks/authored-pytest-result.json b/docs/receiving/explicit-targets-a219f250962c/checks/authored-pytest-result.json
new file mode 100644
index 0000000..313ff5c
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/checks/authored-pytest-result.json
@@ -0,0 +1,37 @@
+{
+ "command": [
+ "/Users/me/testpilot-ci-receiving-3dcb83a1/venv/bin/python",
+ "-B",
+ "-m",
+ "pytest",
+ "-q",
+ "-p",
+ "no:cacheprovider",
+ "--basetemp",
+ "/Users/me/hamon-testpilot-explicit-targets-a219f250962c-receiving/authored-pytest-tmp",
+ "tests"
+ ],
+ "cwd": "/Users/me/hamon-testpilot-explicit-targets-a219f250962c",
+ "python": "/Users/me/testpilot-ci-receiving-3dcb83a1/venv/bin/python",
+ "returncode": 1,
+ "duration_s": 25.187,
+ "source_unchanged": true,
+ "source_sha256": {
+ "/Users/me/hamon-testpilot-explicit-targets-a219f250962c/testpilot/__init__.py": "65c63b80d7da34a83a6049a79c24b8335878cd49cc79951fdefc5f1532ae841f",
+ "/Users/me/hamon-testpilot-explicit-targets-a219f250962c/testpilot/__main__.py": "95f80ec643e30659b6fb1623c303d17246a36e3f3c5cc59cc8723fd71cce3ee1",
+ "/Users/me/hamon-testpilot-explicit-targets-a219f250962c/testpilot/_pytest_generated.py": "d65352d7bade965d7b9dc73efe5d4d01844fa8c7474a1bdcdb3b708f6decf768",
+ "/Users/me/hamon-testpilot-explicit-targets-a219f250962c/testpilot/diff.py": "659021d4a4b462b86eadc25957407b683c733fec60ae33c0703295cb7640a06c",
+ "/Users/me/hamon-testpilot-explicit-targets-a219f250962c/testpilot/html_report.py": "f2b65ee9201f9e91ad183fcb8f6b8bf6e69c77b1a066fcd90bb431951e41a2f6",
+ "/Users/me/hamon-testpilot-explicit-targets-a219f250962c/testpilot/loop.py": "3ea786ae5466ebdd6087b03d829580c333882e6fd429aaf54099320b4aa0bb08",
+ "/Users/me/hamon-testpilot-explicit-targets-a219f250962c/testpilot/model.py": "f6ae697f1f5d686ffdcb96f5451d1a9600d7d41c7bcf324d7e8c276a107e8673",
+ "/Users/me/hamon-testpilot-explicit-targets-a219f250962c/testpilot/sandbox.py": "2e852248ffc238371cf2b11b5797a88c519330db359b2cb969bc61aa72ef1931",
+ "/Users/me/hamon-testpilot-explicit-targets-a219f250962c/testpilot/targets.py": "35add23585b0e88246288572f49bd857ebf4af83a221b9129e784e9b456c1ab0"
+ },
+ "log_sha256": "8331450b67791e32c2c73cce33990b5445fc0213d9c471ac7eb3294abd3883ab",
+ "scope": "Selected existing regression modules plus authored explicit-target tests, not the entire canonical repository suite",
+ "environment": {
+ "PYTHONDONTWRITEBYTECODE": "1",
+ "PYTEST_DISABLE_PLUGIN_AUTOLOAD": "1",
+ "PYTHONPATH": "/Users/me/hamon-testpilot-explicit-targets-a219f250962c"
+ }
+}
diff --git a/docs/receiving/explicit-targets-a219f250962c/checks/authored-pytest.log b/docs/receiving/explicit-targets-a219f250962c/checks/authored-pytest.log
new file mode 100644
index 0000000..534339c
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/checks/authored-pytest.log
@@ -0,0 +1,77 @@
+...................................................... [ 42%]
+........................................................................ [ 98%]
+FF [100%]
+=================================== FAILURES ===================================
+___ test_real_pytest_success_writes_complete_outputs_and_an_applicable_patch ___
+
+tmp_path = PosixPath('/Users/me/hamon-testpilot-explicit-targets-a219f250962c-receiving/authored-pytest-tmp/test_real_pytest_success_write0')
+
+ @pytest.mark.skipif(os.name != "posix", reason="raw filename byte workflow requires POSIX")
+ def test_real_pytest_success_writes_complete_outputs_and_an_applicable_patch(tmp_path):
+> repo, diff, responses, before = raw_filename_repo(tmp_path)
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+tests/test_report_text_encoding.py:124:
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+tests/test_report_text_encoding.py:98: in raw_filename_repo
+ path.write_bytes(b"def measure(value):\n return value + 1\n")
+/opt/homebrew/Cellar/python@3.12/3.12.8/Frameworks/Python.framework/Versions/3.12/lib/python3.12/pathlib.py:1036: in write_bytes
+ with self.open(mode='wb') as f:
+ ^^^^^^^^^^^^^^^^^^^^
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+self = PosixPath('/Users/me/hamon-testpilot-explicit-targets-a219f250962c-receiving/authored-pytest-tmp/test_real_pytest_success_write0/repo/module\udcff.py')
+mode = 'wb', buffering = -1, encoding = None, errors = None, newline = None
+
+ def open(self, mode='r', buffering=-1, encoding=None,
+ errors=None, newline=None):
+ """
+ Open the file pointed to by this path and return a file object, as
+ the built-in open() function does.
+ """
+ if "b" not in mode:
+ encoding = io.text_encoding(encoding)
+> return io.open(self, mode, buffering, encoding, errors, newline)
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+E OSError: [Errno 92] Illegal byte sequence: '/Users/me/hamon-testpilot-explicit-targets-a219f250962c-receiving/authored-pytest-tmp/test_real_pytest_success_write0/repo/module\udcff.py'
+
+/opt/homebrew/Cellar/python@3.12/3.12.8/Frameworks/Python.framework/Versions/3.12/lib/python3.12/pathlib.py:1013: OSError
+___ test_real_cli_preserves_runner_failure_and_still_writes_readable_report ____
+
+tmp_path = PosixPath('/Users/me/hamon-testpilot-explicit-targets-a219f250962c-receiving/authored-pytest-tmp/test_real_cli_preserves_runner0')
+
+ @pytest.mark.skipif(os.name != "posix" or not coverage_available(sys.executable),
+ reason="raw filename CLI coverage workflow requires POSIX and coverage")
+ def test_real_cli_preserves_runner_failure_and_still_writes_readable_report(tmp_path):
+> repo, diff, responses, before = raw_filename_repo(tmp_path)
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+tests/test_report_text_encoding.py:152:
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+tests/test_report_text_encoding.py:98: in raw_filename_repo
+ path.write_bytes(b"def measure(value):\n return value + 1\n")
+/opt/homebrew/Cellar/python@3.12/3.12.8/Frameworks/Python.framework/Versions/3.12/lib/python3.12/pathlib.py:1036: in write_bytes
+ with self.open(mode='wb') as f:
+ ^^^^^^^^^^^^^^^^^^^^
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+self = PosixPath('/Users/me/hamon-testpilot-explicit-targets-a219f250962c-receiving/authored-pytest-tmp/test_real_cli_preserves_runner0/repo/module\udcff.py')
+mode = 'wb', buffering = -1, encoding = None, errors = None, newline = None
+
+ def open(self, mode='r', buffering=-1, encoding=None,
+ errors=None, newline=None):
+ """
+ Open the file pointed to by this path and return a file object, as
+ the built-in open() function does.
+ """
+ if "b" not in mode:
+ encoding = io.text_encoding(encoding)
+> return io.open(self, mode, buffering, encoding, errors, newline)
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+E OSError: [Errno 92] Illegal byte sequence: '/Users/me/hamon-testpilot-explicit-targets-a219f250962c-receiving/authored-pytest-tmp/test_real_cli_preserves_runner0/repo/module\udcff.py'
+
+/opt/homebrew/Cellar/python@3.12/3.12.8/Frameworks/Python.framework/Versions/3.12/lib/python3.12/pathlib.py:1013: OSError
+=========================== short test summary info ============================
+FAILED tests/test_report_text_encoding.py::test_real_pytest_success_writes_complete_outputs_and_an_applicable_patch
+FAILED tests/test_report_text_encoding.py::test_real_cli_preserves_runner_failure_and_still_writes_readable_report
+2 failed, 126 passed, 18 subtests passed in 25.00s
diff --git a/docs/receiving/explicit-targets-a219f250962c/checks/baseline-platform-control.json b/docs/receiving/explicit-targets-a219f250962c/checks/baseline-platform-control.json
new file mode 100644
index 0000000..e8cfd91
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/checks/baseline-platform-control.json
@@ -0,0 +1,23 @@
+{
+ "canonical_baseline": "e2285d68b2ea5eb2158c0a7e0d936ce5d56f8ff5",
+ "scoped_baseline": "a67abc30301dbcb357e866de749502045e5e40fd",
+ "command": [
+ "/Users/me/testpilot-ci-receiving-3dcb83a1/venv/bin/python",
+ "-B",
+ "-m",
+ "pytest",
+ "-q",
+ "-p",
+ "no:cacheprovider",
+ "--basetemp",
+ "/Users/me/hamon-testpilot-explicit-targets-a219f250962c-receiving/baseline-platform-tmp",
+ "tests/test_report_text_encoding.py::test_real_pytest_success_writes_complete_outputs_and_an_applicable_patch",
+ "tests/test_report_text_encoding.py::test_real_cli_preserves_runner_failure_and_still_writes_readable_report"
+ ],
+ "cwd": "/Users/me/hamon-testpilot-explicit-targets-a219f250962c-receiving/scoped-baseline",
+ "returncode": 1,
+ "expected_fixture_failure": true,
+ "reason": "Both unchanged POSIX-only test fixtures attempt a non-UTF-8 filename that macOS refuses at Path.write_bytes before TestPilot is invoked. Baseline reproduces the same two failures.",
+ "test_source_sha256": "1dde3e4f3b227918e541e4ffc71099a8f36c235c198169bb1a152302ea635a3e",
+ "log_sha256": "6a6f94c23215b7af7ecdd2faba8a318557ea3105048d2dd627fd68205b301257"
+}
diff --git a/docs/receiving/explicit-targets-a219f250962c/checks/baseline-platform-control.log b/docs/receiving/explicit-targets-a219f250962c/checks/baseline-platform-control.log
new file mode 100644
index 0000000..c11fa2a
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/checks/baseline-platform-control.log
@@ -0,0 +1,75 @@
+FF [100%]
+=================================== FAILURES ===================================
+___ test_real_pytest_success_writes_complete_outputs_and_an_applicable_patch ___
+
+tmp_path = PosixPath('/Users/me/hamon-testpilot-explicit-targets-a219f250962c-receiving/baseline-platform-tmp/test_real_pytest_success_write0')
+
+ @pytest.mark.skipif(os.name != "posix", reason="raw filename byte workflow requires POSIX")
+ def test_real_pytest_success_writes_complete_outputs_and_an_applicable_patch(tmp_path):
+> repo, diff, responses, before = raw_filename_repo(tmp_path)
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+tests/test_report_text_encoding.py:124:
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+tests/test_report_text_encoding.py:98: in raw_filename_repo
+ path.write_bytes(b"def measure(value):\n return value + 1\n")
+/opt/homebrew/Cellar/python@3.12/3.12.8/Frameworks/Python.framework/Versions/3.12/lib/python3.12/pathlib.py:1036: in write_bytes
+ with self.open(mode='wb') as f:
+ ^^^^^^^^^^^^^^^^^^^^
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+self = PosixPath('/Users/me/hamon-testpilot-explicit-targets-a219f250962c-receiving/baseline-platform-tmp/test_real_pytest_success_write0/repo/module\udcff.py')
+mode = 'wb', buffering = -1, encoding = None, errors = None, newline = None
+
+ def open(self, mode='r', buffering=-1, encoding=None,
+ errors=None, newline=None):
+ """
+ Open the file pointed to by this path and return a file object, as
+ the built-in open() function does.
+ """
+ if "b" not in mode:
+ encoding = io.text_encoding(encoding)
+> return io.open(self, mode, buffering, encoding, errors, newline)
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+E OSError: [Errno 92] Illegal byte sequence: '/Users/me/hamon-testpilot-explicit-targets-a219f250962c-receiving/baseline-platform-tmp/test_real_pytest_success_write0/repo/module\udcff.py'
+
+/opt/homebrew/Cellar/python@3.12/3.12.8/Frameworks/Python.framework/Versions/3.12/lib/python3.12/pathlib.py:1013: OSError
+___ test_real_cli_preserves_runner_failure_and_still_writes_readable_report ____
+
+tmp_path = PosixPath('/Users/me/hamon-testpilot-explicit-targets-a219f250962c-receiving/baseline-platform-tmp/test_real_cli_preserves_runner0')
+
+ @pytest.mark.skipif(os.name != "posix" or not coverage_available(sys.executable),
+ reason="raw filename CLI coverage workflow requires POSIX and coverage")
+ def test_real_cli_preserves_runner_failure_and_still_writes_readable_report(tmp_path):
+> repo, diff, responses, before = raw_filename_repo(tmp_path)
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+tests/test_report_text_encoding.py:152:
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+tests/test_report_text_encoding.py:98: in raw_filename_repo
+ path.write_bytes(b"def measure(value):\n return value + 1\n")
+/opt/homebrew/Cellar/python@3.12/3.12.8/Frameworks/Python.framework/Versions/3.12/lib/python3.12/pathlib.py:1036: in write_bytes
+ with self.open(mode='wb') as f:
+ ^^^^^^^^^^^^^^^^^^^^
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+self = PosixPath('/Users/me/hamon-testpilot-explicit-targets-a219f250962c-receiving/baseline-platform-tmp/test_real_cli_preserves_runner0/repo/module\udcff.py')
+mode = 'wb', buffering = -1, encoding = None, errors = None, newline = None
+
+ def open(self, mode='r', buffering=-1, encoding=None,
+ errors=None, newline=None):
+ """
+ Open the file pointed to by this path and return a file object, as
+ the built-in open() function does.
+ """
+ if "b" not in mode:
+ encoding = io.text_encoding(encoding)
+> return io.open(self, mode, buffering, encoding, errors, newline)
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+E OSError: [Errno 92] Illegal byte sequence: '/Users/me/hamon-testpilot-explicit-targets-a219f250962c-receiving/baseline-platform-tmp/test_real_cli_preserves_runner0/repo/module\udcff.py'
+
+/opt/homebrew/Cellar/python@3.12/3.12.8/Frameworks/Python.framework/Versions/3.12/lib/python3.12/pathlib.py:1013: OSError
+=========================== short test summary info ============================
+FAILED tests/test_report_text_encoding.py::test_real_pytest_success_writes_complete_outputs_and_an_applicable_patch
+FAILED tests/test_report_text_encoding.py::test_real_cli_preserves_runner_failure_and_still_writes_readable_report
+2 failed in 0.17s
diff --git a/docs/receiving/explicit-targets-a219f250962c/checks/bundle-verify.log b/docs/receiving/explicit-targets-a219f250962c/checks/bundle-verify.log
new file mode 100644
index 0000000..f8224fd
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/checks/bundle-verify.log
@@ -0,0 +1,7 @@
+The bundle contains these 3 refs:
+00b63c8e03cc515160da1202331224167d12d253 refs/heads/current-main-receiving
+fc24a55d2c5e2d94478072e8e6736bf44bab81a6 refs/heads/main
+00b63c8e03cc515160da1202331224167d12d253 HEAD
+The bundle records a complete history.
+The bundle uses this hash algorithm: sha1
+/Users/me/hamon-testpilot-explicit-targets-a219f250962c-receiving/packet/source/source-history.bundle is okay
diff --git a/docs/receiving/explicit-targets-a219f250962c/checks/current-composition-pytest-result.json b/docs/receiving/explicit-targets-a219f250962c/checks/current-composition-pytest-result.json
new file mode 100644
index 0000000..6816364
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/checks/current-composition-pytest-result.json
@@ -0,0 +1,38 @@
+{
+ "canonical_base": "63174afd8e63a0ea562cf188b7d971522f017a30",
+ "scoped_base": "33a10cd5b756b8ade8768bd6f8da75f55da369c3",
+ "candidate_index_tree": "d1ba76e3872a0e843821b799f8accb69aa32416d",
+ "command": [
+ "/Users/me/testpilot-ci-receiving-3dcb83a1/venv/bin/python",
+ "-B",
+ "-m",
+ "pytest",
+ "-q",
+ "-p",
+ "no:cacheprovider",
+ "--basetemp",
+ "/Users/me/hamon-testpilot-explicit-targets-a219f250962c-receiving/current-composition-pytest-tmp",
+ "tests/test_explicit_targets.py",
+ "tests/test_explicit_target_cli.py",
+ "tests/test_cli_targets.py",
+ "tests/test_cli_diff_transport.py",
+ "tests/test_recheck.py"
+ ],
+ "returncode": 1,
+ "duration_s": 20.737,
+ "source_unchanged": true,
+ "source_sha256": {
+ "targets.py": "35add23585b0e88246288572f49bd857ebf4af83a221b9129e784e9b456c1ab0",
+ "html_report.py": "f2b65ee9201f9e91ad183fcb8f6b8bf6e69c77b1a066fcd90bb431951e41a2f6",
+ "__init__.py": "65c63b80d7da34a83a6049a79c24b8335878cd49cc79951fdefc5f1532ae841f",
+ "model.py": "f6ae697f1f5d686ffdcb96f5451d1a9600d7d41c7bcf324d7e8c276a107e8673",
+ "sandbox.py": "8d5aadafff5188473ec45eb1cd8f01fb562836ff05bf12a27939cde83b33472a",
+ "loop.py": "3ea786ae5466ebdd6087b03d829580c333882e6fd429aaf54099320b4aa0bb08",
+ "diff.py": "659021d4a4b462b86eadc25957407b683c733fec60ae33c0703295cb7640a06c",
+ "recheck.py": "1ecbe0ff7da7bb4a8117d730a76624c8929e0e4b10cb9d502ab7358cb38d8f2e",
+ "_pytest_generated.py": "d65352d7bade965d7b9dc73efe5d4d01844fa8c7474a1bdcdb3b708f6decf768",
+ "__main__.py": "319bc6e8b06615d657530eb35fa2f7f3e6a883a36c98ea1540fdf4c2815eac55"
+ },
+ "log_sha256": "8d4aad87fb875bc8d3521699103bb76269a777da8ab84b0eb2b985475916fe49",
+ "scope": "Focused current-owner composition: explicit capability, shared CLI preview/raw transport and preserved saved-test recheck; no canonical whole-suite claim"
+}
diff --git a/docs/receiving/explicit-targets-a219f250962c/checks/current-composition-pytest.log b/docs/receiving/explicit-targets-a219f250962c/checks/current-composition-pytest.log
new file mode 100644
index 0000000..188d3fe
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/checks/current-composition-pytest.log
@@ -0,0 +1,28 @@
+...................................................F.. [ 90%]
+.....s [100%]
+=================================== FAILURES ===================================
+___________ test_configured_suite_and_fixtures_remain_part_of_result ___________
+
+project = PosixPath('/Users/me/hamon-testpilot-explicit-targets-a219f250962c-receiving/current-composition-pytest-tmp/test_configured_suite_and_fixt0/project')
+tmp_path = PosixPath('/Users/me/hamon-testpilot-explicit-targets-a219f250962c-receiving/current-composition-pytest-tmp/test_configured_suite_and_fixt0')
+
+ def test_configured_suite_and_fixtures_remain_part_of_result(project, tmp_path):
+ (project / "pytest.ini").write_text("[pytest]\ntestpaths = spec\n")
+ (project / "spec").mkdir()
+ (project / "spec/test_current.py").write_text("def test_current():\n assert False\n")
+ (project / "conftest.py").write_text(
+ "import pytest\n@pytest.fixture\ndef current_value():\n return 17\n")
+ report = save_report(tmp_path / "report.json",
+ "def test_retained(current_value):\n assert current_value == 17\n")
+ result = recheck.recheck_report(project, report, timeout_s=10)
+ assert result["status"] == "failed"
+ assert result["final"]["generated"]["passed"] == 1
+ assert result["final"]["failed"] == 1
+> assert result["final"]["passed"] == 1 # ignored tests/ existing case stays ignored
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+E assert 2 == 1
+
+tests/test_recheck.py:189: AssertionError
+=========================== short test summary info ============================
+FAILED tests/test_recheck.py::test_configured_suite_and_fixtures_remain_part_of_result
+1 failed, 58 passed, 1 skipped, 18 subtests passed in 20.47s
diff --git a/docs/receiving/explicit-targets-a219f250962c/checks/current-parent-control.json b/docs/receiving/explicit-targets-a219f250962c/checks/current-parent-control.json
new file mode 100644
index 0000000..9f47d73
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/checks/current-parent-control.json
@@ -0,0 +1,24 @@
+{
+ "canonical_baseline": "63174afd8e63a0ea562cf188b7d971522f017a30",
+ "scoped_baseline": "33a10cd5b756b8ade8768bd6f8da75f55da369c3",
+ "command": [
+ "/Users/me/testpilot-ci-receiving-3dcb83a1/venv/bin/python",
+ "-B",
+ "-m",
+ "pytest",
+ "-q",
+ "-p",
+ "no:cacheprovider",
+ "--basetemp",
+ "/Users/me/hamon-testpilot-explicit-targets-a219f250962c-receiving/current-parent-control-tmp",
+ "tests/test_recheck.py::test_configured_suite_and_fixtures_remain_part_of_result"
+ ],
+ "returncode": 1,
+ "same_preexisting_failure": true,
+ "source_sha256": {
+ "testpilot/sandbox.py": "8d5aadafff5188473ec45eb1cd8f01fb562836ff05bf12a27939cde83b33472a",
+ "testpilot/recheck.py": "1ecbe0ff7da7bb4a8117d730a76624c8929e0e4b10cb9d502ab7358cb38d8f2e",
+ "tests/test_recheck.py": "dc74c36ddc2f2bd62505549648d0572e5a388840076c8965338f23d371173760"
+ },
+ "log_sha256": "f85c73fa065e8ceadef1c940eb342c3f17f35c6711f4ed9d636f70ce627a453a"
+}
diff --git a/docs/receiving/explicit-targets-a219f250962c/checks/current-parent-control.log b/docs/receiving/explicit-targets-a219f250962c/checks/current-parent-control.log
new file mode 100644
index 0000000..0dc5236
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/checks/current-parent-control.log
@@ -0,0 +1,27 @@
+F [100%]
+=================================== FAILURES ===================================
+___________ test_configured_suite_and_fixtures_remain_part_of_result ___________
+
+project = PosixPath('/Users/me/hamon-testpilot-explicit-targets-a219f250962c-receiving/current-parent-control-tmp/test_configured_suite_and_fixt0/project')
+tmp_path = PosixPath('/Users/me/hamon-testpilot-explicit-targets-a219f250962c-receiving/current-parent-control-tmp/test_configured_suite_and_fixt0')
+
+ def test_configured_suite_and_fixtures_remain_part_of_result(project, tmp_path):
+ (project / "pytest.ini").write_text("[pytest]\ntestpaths = spec\n")
+ (project / "spec").mkdir()
+ (project / "spec/test_current.py").write_text("def test_current():\n assert False\n")
+ (project / "conftest.py").write_text(
+ "import pytest\n@pytest.fixture\ndef current_value():\n return 17\n")
+ report = save_report(tmp_path / "report.json",
+ "def test_retained(current_value):\n assert current_value == 17\n")
+ result = recheck.recheck_report(project, report, timeout_s=10)
+ assert result["status"] == "failed"
+ assert result["final"]["generated"]["passed"] == 1
+ assert result["final"]["failed"] == 1
+> assert result["final"]["passed"] == 1 # ignored tests/ existing case stays ignored
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+E assert 2 == 1
+
+tests/test_recheck.py:189: AssertionError
+=========================== short test summary info ============================
+FAILED tests/test_recheck.py::test_configured_suite_and_fixtures_remain_part_of_result
+1 failed in 0.23s
diff --git a/docs/receiving/explicit-targets-a219f250962c/checks/owner-sandbox-delta.patch b/docs/receiving/explicit-targets-a219f250962c/checks/owner-sandbox-delta.patch
new file mode 100644
index 0000000..0db54ad
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/checks/owner-sandbox-delta.patch
@@ -0,0 +1,39 @@
+--- e228/testpilot/sandbox.py
++++ 631/testpilot/sandbox.py
+@@ -223,8 +223,13 @@
+
+ def run_pytest(repo: str | Path, extra_files: dict[str, str] | None = None, *, timeout: float = 60.0,
+ python: str | None = None, coverage: bool | None = None,
+- pytest_args: tuple[str, ...] = ()) -> SandboxResult:
+- """Copy ``repo`` to a temp dir, add ``extra_files``, run pytest, collect results."""
++ pytest_args: tuple[str, ...] = (), exact_files: bool = False) -> SandboxResult:
++ """Copy repo, add tests, run pytest and collect native results.
++
++ exact_files admits retained UTF-8 bytes: matching copied files are reused,
++ different copied files refuse, and new files use binary output. The default
++ generation path retains its existing text-write behavior.
++ """
+ python = python or sys.executable
+ use_cov = coverage_available(python) if coverage is None else coverage
+ with tempfile.TemporaryDirectory(prefix="testpilot-") as td:
+@@ -234,7 +239,19 @@
+ for rel, content in (extra_files or {}).items():
+ dest = _safe_join(work, rel)
+ dest.parent.mkdir(parents=True, exist_ok=True)
+- dest.write_text(content, encoding="utf-8")
++ if exact_files:
++ data = content.encode("utf-8")
++ if dest.exists():
++ if not dest.is_file() or dest.stat().st_size != len(data):
++ raise ValueError(f"copied retained test differs: {rel}")
++ with dest.open("rb") as stream:
++ if stream.read(len(data) + 1) != data:
++ raise ValueError(f"copied retained test differs: {rel}")
++ else:
++ with dest.open("xb") as stream:
++ stream.write(data)
++ else:
++ dest.write_text(content, encoding="utf-8")
+ junit = tdp / "junit.xml"
+ options = ["-q", "-p", "no:cacheprovider", "--rootdir", str(work), "-o", "addopts=",
+ "-o", "junit_family=xunit2", f"--junitxml={junit}", *pytest_args]
diff --git a/docs/receiving/explicit-targets-a219f250962c/checks/source-patch-receiving.json b/docs/receiving/explicit-targets-a219f250962c/checks/source-patch-receiving.json
new file mode 100644
index 0000000..482722b
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/checks/source-patch-receiving.json
@@ -0,0 +1,12 @@
+{
+ "source_commit": "00b63c8e03cc515160da1202331224167d12d253",
+ "source_tree": "d1ba76e3872a0e843821b799f8accb69aa32416d",
+ "scoped_base": "33a10cd5b756b8ade8768bd6f8da75f55da369c3",
+ "canonical_base": "63174afd8e63a0ea562cf188b7d971522f017a30",
+ "patch_sha256": "61724e6514a5cf346259d93ee1937c929505fe9fafab0bb5fc51686e090cb870",
+ "git_apply_check_passed": true,
+ "git_apply_index_passed": true,
+ "all_27_received_file_hashes_match": true,
+ "all_19_untouched_parent_files_preserved": true,
+ "fixture_removed_after_receipt": true
+}
diff --git a/docs/receiving/explicit-targets-a219f250962c/coordination/recheck-owner-note.json b/docs/receiving/explicit-targets-a219f250962c/coordination/recheck-owner-note.json
new file mode 100644
index 0000000..e9e5e15
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/coordination/recheck-owner-note.json
@@ -0,0 +1,5 @@
+{
+ "repo_full_name": "Jacob-Met/testpilot",
+ "pr_number": 28,
+ "comment": "Native receiving observation for the saved-test recheck owner, from explicit-target scope #30:\n\nAt exact canonical main `63174afd8e63a0ea562cf188b7d971522f017a30`, the unchanged `tests/test_recheck.py::test_configured_suite_and_fixtures_remain_part_of_result` returns `final.passed == 2` where its assertion expects 1. The retained generated pass is 1 and the configured failing case is 1, as expected by the preceding assertions. The same single-case failure reproduces in an untouched scoped materialization of 631 and in the explicit-target composition. This identifies an inherited native receiving discrepancy; it does not attribute a cause or change recheck behavior.\n\nEnvironment: existing Mac Python 3.12.8, pytest 9.1.1, coverage 7.16.2; `PYTHONDONTWRITEBYTECODE=1`, `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1`; exact source-root `PYTHONPATH`. No installation. Source `recheck.py`, `sandbox.py`, `_pytest_generated.py` and the test remain byte-exact canonical inputs.\n\nThe paired raw outputs and source hashes are retained as `checks/current-composition-pytest.log`, `checks/current-parent-control.log` and the corresponding JSON receipts in the explicit-target receiving packet. The new target capability independently passed its six-group actual native CLI/prompt/sandbox/patch gate on Python 3.13.7. No recheck repair was made within #30's scope."
+}
diff --git a/docs/receiving/explicit-targets-a219f250962c/independent/REVIEW.md b/docs/receiving/explicit-targets-a219f250962c/independent/REVIEW.md
new file mode 100644
index 0000000..12d6c87
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/independent/REVIEW.md
@@ -0,0 +1,43 @@
+# Independent TestPilot explicit-target receiving
+
+**Disposition: ACCEPT** source commit `00b63c8e03cc515160da1202331224167d12d253`, tree `d1ba76e3872a0e843821b799f8accb69aa32416d`.
+
+This is the author's 27-leaf scoped composition over canonical `63174afd8e63a0ea562cf188b7d971522f017a30`. It includes the already merged recheck command and unchanged owner sandbox. Root inspected the new resolver and exact CLI/loop/HTML seams, checked the clean source pin, then executed the independent receiver on an existing Python 3.13.7 environment with pytest 9.1.1 and coverage 7.16.2. No dependency was installed.
+
+## Actual receiving
+
+All six independent groups passed, with 23 actual CLI, Git and pytest commands. The command-line default preview was byte-identical to the unchanged e228 preview on the same real Git diff, selecting only the changed `ignored` function. Explicit preview instead selected `Policy.cap` and `accepts` in caller order, deduplicated the repeated method, retained every original requested argument and exact loaded diff, and left both selected unchanged spans' changed-line lists empty.
+
+An actual CLI generation used the unmodified native `ScriptedModel`. A narrow observation wrapper called the real `make_client`, returned its untouched client, and recorded its real outgoing messages; it did not replace model responses or sandbox behavior. The deliberately failing first generated test triggered the real repair loop. Planner, editor and repair messages each contained the exact diff and selected source. Final native execution passed the one existing and two generated tests. This is deterministic workflow receiving, not a claim about live-model generation quality.
+
+The actual exported `testpilot.patch` passed `git apply --check`, applied to a separate fresh Git fixture, and its three-test suite passed through the existing native pytest interpreter. The source and existing test bytes stayed unchanged. Patch SHA256: `9e53e5ac4daa21d8ee93a51c94a60aec14fbee520d48ef683517fc250a456800`.
+
+Ten invalid target cases returned exit 2 before model calls or output-directory creation: missing function, conditional duplicate name, test source, parent traversal, absolute path, outside symlink, non-Python source, syntax error, malformed spec and empty qualified name. The native fixture source and outside file were unchanged. A separate real stdin preview read Latin-1 source using its encoding cookie and Unicode function name, retained exact diff context, and selected a module that would raise immediately if imported; selection did not execute it.
+
+The independent receiver made no candidate-source edits. All candidate package and baseline source fingerprints were unchanged after execution. Temporary synthetic repositories were removed by the receiver; exact fixture definitions, generated patch and native receipts remain in this packet. The receiver completed with exit 0 in 5.89 seconds. Its immediate ordinary-free-space preflight was 2,145,505,280 bytes.
+
+## Evidence and qualifications
+
+- `receive_explicit_targets.py` is the exact independent script, SHA256 `08dc3a0c30cb559e53bc644de966b13dbcafb7bb3c841886963261e28832cf5d`.
+- `receiving-result.json` retains all 23 command arguments, output/error streams, exit codes, group assertions, native report and source fingerprints.
+- `model-calls/` retains actual client-call observations, including empty refusal observations.
+- `native-run/` retains the actual generated report JSON, Markdown, HTML and patch.
+- `source-freeze.json` is a byte-identical copy of the author's frozen 27-leaf manifest.
+- `source-binding.json` independently binds all 27 current native leaves to the clean received commit and tree.
+
+The author's wider tests and paired parent controls retain their original pins: e228 authored source passed 126 tests plus 18 subtests; two raw-filename fixture failures reproduced before TestPilot invocation on unchanged e228 under macOS. Current631 focused receiving passed 58 tests plus 18 subtests with one existing skip; the existing recheck configured-suite count failure also reproduced on unchanged631. Root did not change or rerun those unrelated owner tests. Those controls qualify the environment and are preserved in the author packet.
+
+The independent default-preview comparison used the untouched e228 import root; the explicit runtime, generation and patch path used the complete current631 composition. This distinction is deliberate and is not a claim of running all historical tests on the new public tree.
+
+## Reproduce
+
+Use a clean source checkout matching the frozen package and an existing supported Python with pytest/coverage. Run the preserved script with explicit `--source`, `--baseline-source` and a new `--out` directory. The original invocation used:
+
+```text
+/Users/me/testpilot-html-066deeadcc8b/venv/bin/python receive_explicit_targets.py
+ --source /Users/me/hamon-testpilot-explicit-targets-a219f250962c
+ --baseline-source /Users/me/hamon-testpilot-target-assessment-a219f250962c/current/snapshot
+ --out /Users/me/testpilot-explicit-target-review-a219f250962c/run-01
+```
+
+Both `PYTHONDONTWRITEBYTECODE=1` and `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1` were set. Replays must use a new output directory and preserve existing receipts. No further behavior rerun is needed for identical source bytes.
diff --git a/docs/receiving/explicit-targets-a219f250962c/independent/SHA256SUMS.json b/docs/receiving/explicit-targets-a219f250962c/independent/SHA256SUMS.json
new file mode 100644
index 0000000..b4a4a4e
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/independent/SHA256SUMS.json
@@ -0,0 +1,105 @@
+{
+ "schema": "testpilot.independent.packet.v1",
+ "files": [
+ {
+ "path": "REVIEW.md",
+ "bytes": 5325,
+ "sha256": "7ce638499b5ede479e7b4aa59abffaaa94ae2c1bcc9b1405a06476c6d2a16ef8"
+ },
+ {
+ "path": "model-calls/explicit-run-model-calls.json",
+ "bytes": 5220,
+ "sha256": "a92be45f0279b65a2ad5f614126c078d65f6c18246a9d835323546e2c6605a42"
+ },
+ {
+ "path": "model-calls/refusal-0-model-calls.json",
+ "bytes": 3,
+ "sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570"
+ },
+ {
+ "path": "model-calls/refusal-1-model-calls.json",
+ "bytes": 3,
+ "sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570"
+ },
+ {
+ "path": "model-calls/refusal-2-model-calls.json",
+ "bytes": 3,
+ "sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570"
+ },
+ {
+ "path": "model-calls/refusal-3-model-calls.json",
+ "bytes": 3,
+ "sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570"
+ },
+ {
+ "path": "model-calls/refusal-4-model-calls.json",
+ "bytes": 3,
+ "sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570"
+ },
+ {
+ "path": "model-calls/refusal-5-model-calls.json",
+ "bytes": 3,
+ "sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570"
+ },
+ {
+ "path": "model-calls/refusal-6-model-calls.json",
+ "bytes": 3,
+ "sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570"
+ },
+ {
+ "path": "model-calls/refusal-7-model-calls.json",
+ "bytes": 3,
+ "sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570"
+ },
+ {
+ "path": "model-calls/refusal-8-model-calls.json",
+ "bytes": 3,
+ "sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570"
+ },
+ {
+ "path": "model-calls/refusal-9-model-calls.json",
+ "bytes": 3,
+ "sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570"
+ },
+ {
+ "path": "native-run/report.html",
+ "bytes": 33952,
+ "sha256": "5efe4f027412d299a10b73af853961f816900b850b4bdfac36ff5166cf6476b2"
+ },
+ {
+ "path": "native-run/report.json",
+ "bytes": 9728,
+ "sha256": "7376864902ec48d609ce2b787033ae3e3a26148d55641ff1da378c78aa6f5dbb"
+ },
+ {
+ "path": "native-run/report.md",
+ "bytes": 1017,
+ "sha256": "b79854ef4ba5821d117c9ff3b201bec8396e7d9158636e0f640d2dd65d7ee896"
+ },
+ {
+ "path": "native-run/testpilot.patch",
+ "bytes": 336,
+ "sha256": "9e53e5ac4daa21d8ee93a51c94a60aec14fbee520d48ef683517fc250a456800"
+ },
+ {
+ "path": "receive_explicit_targets.py",
+ "bytes": 17612,
+ "sha256": "08dc3a0c30cb559e53bc644de966b13dbcafb7bb3c841886963261e28832cf5d"
+ },
+ {
+ "path": "receiving-result.json",
+ "bytes": 45690,
+ "sha256": "b47fab55bcc44de2179cfd10c91238b5d1557180d608e21ec6fb3768e4febdd7"
+ },
+ {
+ "path": "source-binding.json",
+ "bytes": 7598,
+ "sha256": "f7e08b33eea94a82edfeb6fd61aac5b7d47608d3c2741e4bf6377519a38a703c"
+ },
+ {
+ "path": "source-freeze.json",
+ "bytes": 7754,
+ "sha256": "e916c8da533841efde3666e3f3034a1d579315ec4ea54953f273f1b2db30b8d7"
+ }
+ ]
+}
diff --git a/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/explicit-run-model-calls.json b/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/explicit-run-model-calls.json
new file mode 100644
index 0000000..0a3e707
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/explicit-run-model-calls.json
@@ -0,0 +1,46 @@
+[
+ {
+ "type": "ScriptedModel",
+ "calls": [
+ [
+ "nvidia/nemotron-3-super-120b-a12b",
+ [
+ {
+ "role": "system",
+ "content": "You are TestPilot's planner. Given caller-selected Python functions and a supplied diff, list the behaviours that pytest tests should pin down: normal cases, edge cases, error cases. Be concise. Do not write code. These functions were explicitly chosen and may be unchanged."
+ },
+ {
+ "role": "user",
+ "content": "Caller selected these targets; no dependency inference was performed.\n\nCaller-selected functions:\n\n### settings.py::Policy.cap (lines 13-15; import with `from settings import Policy`)\n```python\n @staticmethod\n def cap(amount, limit=LIMIT):\n return min(amount, limit)\n```\n\n### settings.py::accepts (lines 4-5; import with `from settings import accepts`)\n```python\ndef accepts(amount):\n return amount <= LIMIT\n```\n\nSupplied diff context:\n```diff\ndiff --git a/settings.py b/settings.py\nindex 6a8ed7c..572bbe2 100644\n--- a/settings.py\n+++ b/settings.py\n@@ -1,4 +1,4 @@\n-LIMIT = 10\n+LIMIT = 7\n \n \n def accepts(amount):\n@@ -6,7 +6,7 @@ def accepts(amount):\n \n \n def ignored(amount):\n- return amount + 5\n+ return amount + 6\n \n \n class Policy:\n\n```\n\nWrite the test plan."
+ }
+ ]
+ ],
+ [
+ "nvidia/Nemotron-3_5-Lightning",
+ [
+ {
+ "role": "system",
+ "content": "You are TestPilot's test writer. Write pytest tests for the caller-selected functions. Import the code under test by its module path. Tests must be deterministic, fast, offline, and must not sleep or touch the network. Output each file as a fenced block whose info string is `python path=tests/test_.py`. Add new test files; do not replace the repository's existing tests. Output only test files. These functions were explicitly chosen and may be unchanged."
+ },
+ {
+ "role": "user",
+ "content": "Test plan:\nPin only the caller-selected boundary and captured default.\n\nCaller selected these targets; no dependency inference was performed.\n\nCaller-selected functions:\n\n### settings.py::Policy.cap (lines 13-15; import with `from settings import Policy`)\n```python\n @staticmethod\n def cap(amount, limit=LIMIT):\n return min(amount, limit)\n```\n\n### settings.py::accepts (lines 4-5; import with `from settings import accepts`)\n```python\ndef accepts(amount):\n return amount <= LIMIT\n```\n\nSupplied diff context:\n```diff\ndiff --git a/settings.py b/settings.py\nindex 6a8ed7c..572bbe2 100644\n--- a/settings.py\n+++ b/settings.py\n@@ -1,4 +1,4 @@\n-LIMIT = 10\n+LIMIT = 7\n \n \n def accepts(amount):\n@@ -6,7 +6,7 @@ def accepts(amount):\n \n \n def ignored(amount):\n- return amount + 5\n+ return amount + 6\n \n \n class Policy:\n\n```\n\nWrite the pytest file(s)."
+ }
+ ]
+ ],
+ [
+ "nvidia/Nemotron-3_5-Lightning",
+ [
+ {
+ "role": "system",
+ "content": "You are TestPilot's test repairer. The tests below failed. If the TEST is wrong (bad expectation, import, fixture, timing), output the corrected files in the same fenced format, using the current generated file paths. Omitted files are kept unchanged; output only the generated files that need repair. Existing repository tests cannot be changed by this workflow. If the failure shows a genuine bug in the CODE under test, do not weaken the test: reply with a line `VERDICT: CODE_BUG` followed by a one-paragraph explanation. These functions were explicitly chosen and may be unchanged."
+ },
+ {
+ "role": "user",
+ "content": "Caller selected these targets; no dependency inference was performed.\n\nCaller-selected functions:\n\n### settings.py::Policy.cap (lines 13-15; import with `from settings import Policy`)\n```python\n @staticmethod\n def cap(amount, limit=LIMIT):\n return min(amount, limit)\n```\n\n### settings.py::accepts (lines 4-5; import with `from settings import accepts`)\n```python\ndef accepts(amount):\n return amount <= LIMIT\n```\n\nSupplied diff context:\n```diff\ndiff --git a/settings.py b/settings.py\nindex 6a8ed7c..572bbe2 100644\n--- a/settings.py\n+++ b/settings.py\n@@ -1,4 +1,4 @@\n-LIMIT = 10\n+LIMIT = 7\n \n \n def accepts(amount):\n@@ -6,7 +6,7 @@ def accepts(amount):\n \n \n def ignored(amount):\n- return amount + 5\n+ return amount + 6\n \n \n class Policy:\n\n```\n\nCurrent tests:\n\n```python path=tests/test_selected.py\nfrom settings import accepts, Policy\n\ndef test_selected_limit():\n assert accepts(8) is True\n\ndef test_selected_default():\n assert Policy.cap(20) == 7\n```\n\nFailure report:\n2 passed, 1 failed, 0 errors, 0 skipped (rc=1); generated: 1 passed of 2 collected\n--- FAILED: test_selected_limit\nassert False is True\n + where False = accepts(8)\ndef test_selected_limit():\n> assert accepts(8) is True\nE assert False is True\nE + where False = accepts(8)\n\ntests/test_selected.py:4: AssertionError"
+ }
+ ]
+ ]
+ ]
+ }
+]
diff --git a/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-0-model-calls.json b/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-0-model-calls.json
new file mode 100644
index 0000000..fe51488
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-0-model-calls.json
@@ -0,0 +1 @@
+[]
diff --git a/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-1-model-calls.json b/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-1-model-calls.json
new file mode 100644
index 0000000..fe51488
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-1-model-calls.json
@@ -0,0 +1 @@
+[]
diff --git a/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-2-model-calls.json b/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-2-model-calls.json
new file mode 100644
index 0000000..fe51488
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-2-model-calls.json
@@ -0,0 +1 @@
+[]
diff --git a/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-3-model-calls.json b/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-3-model-calls.json
new file mode 100644
index 0000000..fe51488
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-3-model-calls.json
@@ -0,0 +1 @@
+[]
diff --git a/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-4-model-calls.json b/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-4-model-calls.json
new file mode 100644
index 0000000..fe51488
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-4-model-calls.json
@@ -0,0 +1 @@
+[]
diff --git a/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-5-model-calls.json b/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-5-model-calls.json
new file mode 100644
index 0000000..fe51488
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-5-model-calls.json
@@ -0,0 +1 @@
+[]
diff --git a/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-6-model-calls.json b/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-6-model-calls.json
new file mode 100644
index 0000000..fe51488
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-6-model-calls.json
@@ -0,0 +1 @@
+[]
diff --git a/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-7-model-calls.json b/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-7-model-calls.json
new file mode 100644
index 0000000..fe51488
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-7-model-calls.json
@@ -0,0 +1 @@
+[]
diff --git a/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-8-model-calls.json b/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-8-model-calls.json
new file mode 100644
index 0000000..fe51488
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-8-model-calls.json
@@ -0,0 +1 @@
+[]
diff --git a/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-9-model-calls.json b/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-9-model-calls.json
new file mode 100644
index 0000000..fe51488
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/independent/model-calls/refusal-9-model-calls.json
@@ -0,0 +1 @@
+[]
diff --git a/docs/receiving/explicit-targets-a219f250962c/independent/native-run/report.html b/docs/receiving/explicit-targets-a219f250962c/independent/native-run/report.html
new file mode 100644
index 0000000..51030e3
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/independent/native-run/report.html
@@ -0,0 +1,174 @@
+TestPilot run review · passed Skip to test results TestPilot / Run review
Passed passed The runner reported a passing selected suite. Passing tests do not establish that the changed code is correct.
Both downloads are embedded in this file. Opening, reviewing or downloading does not run tests or apply the patch.
Generated cases passed / collected 2 / 2
Repair rounds used / limit 1 / 1
Tokens · estimated 1199 Test results Generated files Repair history Plan and scope Coverage Model ledger Patch Final recorded result 3 passed, 0 failed, 0 errors, 0 skipped (rc=0); generated: 2 passed of 2 collected
Return code: 0 · Duration: 0.736 s · Timeout: no
Scroll the table horizontally to read all columns.
Scroll the table horizontally to read all columns.
Recorded test cases passed tests.test_existing::test_existing_stays_in_run Other selected test No diagnostic message recorded.
passed test_selected_limit Generated test No diagnostic message recorded.
passed test_selected_default Generated test No diagnostic message recorded.
Recorded pytest output The runner retains the last 3,000 characters in its report. This is the recorded tail, not a complete process log.
... [100%]
+3 passed in 0.29s
+Final generated files Reported authored-test count: 2. This count is separate from verified generated-case execution above.
tests/test_selected.pyfrom settings import accepts, Policy
+
+def test_selected_limit():
+ assert accepts(7) is True
+ assert accepts(8) is False
+
+def test_selected_default():
+ assert Policy.cap(20) == 7
+Repair history Each round keeps its recorded source snapshot and result. A model response without a new run does not add an execution.
Round 0 · generate Tests at this round tests/test_selected.pyfrom settings import accepts, Policy
+
+def test_selected_limit():
+ assert accepts(8) is True
+
+def test_selected_default():
+ assert Policy.cap(20) == 7
+2 passed, 1 failed, 0 errors, 0 skipped (rc=1); generated: 1 passed of 2 collected
Return code: 1 · Duration: 0.619 s · Timeout: no
Scroll the table horizontally to read all columns.
Scroll the table horizontally to read all columns.
Recorded test cases passed tests.test_existing::test_existing_stays_in_run Other selected test No diagnostic message recorded.
failed test_selected_limit Generated test assert False is True
+ + where False = accepts(8)
+def test_selected_limit():
+> assert accepts(8) is True
+E assert False is True
+E + where False = accepts(8)
+
+tests/test_selected.py:4: AssertionErrorpassed test_selected_default Generated test No diagnostic message recorded.
Recorded pytest output The runner retains the last 3,000 characters in its report. This is the recorded tail, not a complete process log.
.F. [100%]
+=================================== FAILURES ===================================
+_____________________________ test_selected_limit ______________________________
+
+ def test_selected_limit():
+> assert accepts(8) is True
+E assert False is True
+E + where False = accepts(8)
+
+tests/test_selected.py:4: AssertionError
+=========================== short test summary info ============================
+FAILED ../../../../tmp/testpilot-_07ufgwy/repo::test_selected_limit - assert ...
+1 failed, 2 passed in 0.29s
+Round 1 · repair Tests at this round tests/test_selected.pyfrom settings import accepts, Policy
+
+def test_selected_limit():
+ assert accepts(7) is True
+ assert accepts(8) is False
+
+def test_selected_default():
+ assert Policy.cap(20) == 7
+3 passed, 0 failed, 0 errors, 0 skipped (rc=0); generated: 2 passed of 2 collected
Return code: 0 · Duration: 0.736 s · Timeout: no
Scroll the table horizontally to read all columns.
Scroll the table horizontally to read all columns.
Recorded test cases passed tests.test_existing::test_existing_stays_in_run Other selected test No diagnostic message recorded.
passed test_selected_limit Generated test No diagnostic message recorded.
passed test_selected_default Generated test No diagnostic message recorded.
Recorded pytest output The runner retains the last 3,000 characters in its report. This is the recorded tail, not a complete process log.
... [100%]
+3 passed in 0.29s
+Plan and selected source Caller selected these targets; no dependency inference was performed.
Supplied diff context diff --git a/settings.py b/settings.py
+index 6a8ed7c..572bbe2 100644
+--- a/settings.py
++++ b/settings.py
+@@ -1,4 +1,4 @@
+-LIMIT = 10
++LIMIT = 7
+
+
+ def accepts(amount):
+@@ -6,7 +6,7 @@ def accepts(amount):
+
+
+ def ignored(amount):
+- return amount + 5
++ return amount + 6
+
+
+ class Policy:
+Model test plan Pin only the caller-selected boundary and captured default.settings.py::Policy.cap lines 13–15 @staticmethod
+ def cap(amount, limit=LIMIT):
+ return min(amount, limit)settings.py::accepts lines 4–5 def accepts(amount):
+ return amount <= LIMITCoverage Scroll the table horizontally to read all columns.
Total change: 22.2 percentage points · Changed executable lines: 0. Coverage measures executed lines, not assertion quality.
Model and token ledger Token counts are estimated from text length.
Cost is unpriced or incomplete. The recorded subtotal ($0.0 USD) does not establish the full cost.
Scroll the table horizontally to read all columns.
Exact recorded patch diff --git a/tests/test_selected.py b/tests/test_selected.py
+new file mode 100644
+--- /dev/null
++++ b/tests/test_selected.py
+@@ -0,0 +1,8 @@
++from settings import accepts, Policy
++
++def test_selected_limit():
++ assert accepts(7) is True
++ assert accepts(8) is False
++
++def test_selected_default():
++ assert Policy.cap(20) == 7
+This file reviews the recorded TestPilot result. It does not rerun the repository, verify a model diagnosis, or change any source. Text is displayed literally; HTML-incompatible controls and surrogate characters use visible escapes. Embedded downloads retain the original artifact bytes.
Use your browser’s Print command to print the report. No scripts, remote resources, storage or account are required.
diff --git a/docs/receiving/explicit-targets-a219f250962c/independent/native-run/report.json b/docs/receiving/explicit-targets-a219f250962c/independent/native-run/report.json
new file mode 100644
index 0000000..1978144
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/independent/native-run/report.json
@@ -0,0 +1,312 @@
+{
+ "status": "passed",
+ "changed_functions": [
+ {
+ "path": "settings.py",
+ "module": "settings",
+ "qualname": "Policy.cap",
+ "lineno": 13,
+ "end_lineno": 15,
+ "changed_lines": [],
+ "source": " @staticmethod\n def cap(amount, limit=LIMIT):\n return min(amount, limit)",
+ "is_method": true
+ },
+ {
+ "path": "settings.py",
+ "module": "settings",
+ "qualname": "accepts",
+ "lineno": 4,
+ "end_lineno": 5,
+ "changed_lines": [],
+ "source": "def accepts(amount):\n return amount <= LIMIT",
+ "is_method": false
+ }
+ ],
+ "repair_rounds_used": 1,
+ "max_repair_rounds": 1,
+ "test_files": {
+ "tests/test_selected.py": "from settings import accepts, Policy\n\ndef test_selected_limit():\n assert accepts(7) is True\n assert accepts(8) is False\n\ndef test_selected_default():\n assert Policy.cap(20) == 7\n"
+ },
+ "tests_written": 2,
+ "final": {
+ "returncode": 0,
+ "timed_out": false,
+ "duration_s": 0.736,
+ "timeout_s": 15.0,
+ "output": "... [100%]\n3 passed in 0.29s\n",
+ "cases": [
+ {
+ "nodeid": "tests.test_existing::test_existing_stays_in_run",
+ "outcome": "passed",
+ "message": "",
+ "generated_file": null
+ },
+ {
+ "nodeid": "test_selected_limit",
+ "outcome": "passed",
+ "message": "",
+ "generated_file": "tests/test_selected.py"
+ },
+ {
+ "nodeid": "test_selected_default",
+ "outcome": "passed",
+ "message": "",
+ "generated_file": "tests/test_selected.py"
+ }
+ ],
+ "coverage": {
+ "percent_total": 100.0,
+ "files": {
+ "settings.py": {
+ "executed": [
+ 1,
+ 4,
+ 5,
+ 8,
+ 9,
+ 12,
+ 13,
+ 14,
+ 15
+ ],
+ "missing": []
+ }
+ }
+ },
+ "generated_files": [
+ "tests/test_selected.py"
+ ],
+ "junit_available": true,
+ "passed": 3,
+ "failed": 0,
+ "errors": 0,
+ "skipped": 0,
+ "ok": true,
+ "summary": "3 passed, 0 failed, 0 errors, 0 skipped (rc=0); generated: 2 passed of 2 collected",
+ "generated": {
+ "collected": 2,
+ "passed": 2,
+ "failed": 0,
+ "error": 0,
+ "skipped": 0
+ }
+ },
+ "patch": "diff --git a/tests/test_selected.py b/tests/test_selected.py\nnew file mode 100644\n--- /dev/null\n+++ b/tests/test_selected.py\n@@ -0,0 +1,8 @@\n+from settings import accepts, Policy\n+\n+def test_selected_limit():\n+ assert accepts(7) is True\n+ assert accepts(8) is False\n+\n+def test_selected_default():\n+ assert Policy.cap(20) == 7\n",
+ "coverage": {
+ "total_before": 77.8,
+ "total_after": 100.0,
+ "total_delta": 22.2,
+ "changed_lines_executable": 0,
+ "changed_lines_before": null,
+ "changed_lines_after": null
+ },
+ "ledger": {
+ "total_tokens": 1199,
+ "total_cost_usd": 0.0,
+ "cost_is_complete": false,
+ "tokens_estimated": true,
+ "by_model": {
+ "nvidia/nemotron-3-super-120b-a12b": {
+ "calls": 1,
+ "prompt_tokens": 265,
+ "completion_tokens": 15,
+ "cost_usd": 0.0
+ },
+ "nvidia/Nemotron-3_5-Lightning": {
+ "calls": 2,
+ "prompt_tokens": 811,
+ "completion_tokens": 108,
+ "cost_usd": 0.0
+ }
+ },
+ "entries": [
+ {
+ "role": "planner",
+ "model": "nvidia/nemotron-3-super-120b-a12b",
+ "prompt_tokens": 265,
+ "completion_tokens": 15,
+ "estimated": true,
+ "cost_usd": 0.0
+ },
+ {
+ "role": "editor",
+ "model": "nvidia/Nemotron-3_5-Lightning",
+ "prompt_tokens": 332,
+ "completion_tokens": 50,
+ "estimated": true,
+ "cost_usd": 0.0
+ },
+ {
+ "role": "repair",
+ "model": "nvidia/Nemotron-3_5-Lightning",
+ "prompt_tokens": 479,
+ "completion_tokens": 58,
+ "estimated": true,
+ "cost_usd": 0.0
+ }
+ ]
+ },
+ "rounds": [
+ {
+ "round": 0,
+ "kind": "generate",
+ "files": [
+ "tests/test_selected.py"
+ ],
+ "result": {
+ "returncode": 1,
+ "timed_out": false,
+ "duration_s": 0.619,
+ "timeout_s": 15.0,
+ "output": ".F. [100%]\n=================================== FAILURES ===================================\n_____________________________ test_selected_limit ______________________________\n\n def test_selected_limit():\n> assert accepts(8) is True\nE assert False is True\nE + where False = accepts(8)\n\ntests/test_selected.py:4: AssertionError\n=========================== short test summary info ============================\nFAILED ../../../../tmp/testpilot-_07ufgwy/repo::test_selected_limit - assert ...\n1 failed, 2 passed in 0.29s\n",
+ "cases": [
+ {
+ "nodeid": "tests.test_existing::test_existing_stays_in_run",
+ "outcome": "passed",
+ "message": "",
+ "generated_file": null
+ },
+ {
+ "nodeid": "test_selected_limit",
+ "outcome": "failed",
+ "message": "assert False is True\n + where False = accepts(8)\ndef test_selected_limit():\n> assert accepts(8) is True\nE assert False is True\nE + where False = accepts(8)\n\ntests/test_selected.py:4: AssertionError",
+ "generated_file": "tests/test_selected.py"
+ },
+ {
+ "nodeid": "test_selected_default",
+ "outcome": "passed",
+ "message": "",
+ "generated_file": "tests/test_selected.py"
+ }
+ ],
+ "coverage": {
+ "percent_total": 100.0,
+ "files": {
+ "settings.py": {
+ "executed": [
+ 1,
+ 4,
+ 5,
+ 8,
+ 9,
+ 12,
+ 13,
+ 14,
+ 15
+ ],
+ "missing": []
+ }
+ }
+ },
+ "generated_files": [
+ "tests/test_selected.py"
+ ],
+ "junit_available": true,
+ "passed": 2,
+ "failed": 1,
+ "errors": 0,
+ "skipped": 0,
+ "ok": false,
+ "summary": "2 passed, 1 failed, 0 errors, 0 skipped (rc=1); generated: 1 passed of 2 collected",
+ "generated": {
+ "collected": 2,
+ "passed": 1,
+ "failed": 1,
+ "error": 0,
+ "skipped": 0
+ }
+ },
+ "warnings": [],
+ "contents": {
+ "tests/test_selected.py": "from settings import accepts, Policy\n\ndef test_selected_limit():\n assert accepts(8) is True\n\ndef test_selected_default():\n assert Policy.cap(20) == 7\n"
+ }
+ },
+ {
+ "round": 1,
+ "kind": "repair",
+ "files": [
+ "tests/test_selected.py"
+ ],
+ "result": {
+ "returncode": 0,
+ "timed_out": false,
+ "duration_s": 0.736,
+ "timeout_s": 15.0,
+ "output": "... [100%]\n3 passed in 0.29s\n",
+ "cases": [
+ {
+ "nodeid": "tests.test_existing::test_existing_stays_in_run",
+ "outcome": "passed",
+ "message": "",
+ "generated_file": null
+ },
+ {
+ "nodeid": "test_selected_limit",
+ "outcome": "passed",
+ "message": "",
+ "generated_file": "tests/test_selected.py"
+ },
+ {
+ "nodeid": "test_selected_default",
+ "outcome": "passed",
+ "message": "",
+ "generated_file": "tests/test_selected.py"
+ }
+ ],
+ "coverage": {
+ "percent_total": 100.0,
+ "files": {
+ "settings.py": {
+ "executed": [
+ 1,
+ 4,
+ 5,
+ 8,
+ 9,
+ 12,
+ 13,
+ 14,
+ 15
+ ],
+ "missing": []
+ }
+ }
+ },
+ "generated_files": [
+ "tests/test_selected.py"
+ ],
+ "junit_available": true,
+ "passed": 3,
+ "failed": 0,
+ "errors": 0,
+ "skipped": 0,
+ "ok": true,
+ "summary": "3 passed, 0 failed, 0 errors, 0 skipped (rc=0); generated: 2 passed of 2 collected",
+ "generated": {
+ "collected": 2,
+ "passed": 2,
+ "failed": 0,
+ "error": 0,
+ "skipped": 0
+ }
+ },
+ "warnings": [],
+ "contents": {
+ "tests/test_selected.py": "from settings import accepts, Policy\n\ndef test_selected_limit():\n assert accepts(7) is True\n assert accepts(8) is False\n\ndef test_selected_default():\n assert Policy.cap(20) == 7\n"
+ }
+ }
+ ],
+ "plan": "Pin only the caller-selected boundary and captured default.",
+ "message": "",
+ "selection": {
+ "mode": "explicit",
+ "requested": [
+ "settings.py::Policy.cap",
+ "settings.py::accepts",
+ "settings.py::Policy.cap"
+ ],
+ "reason": "Caller selected these targets; no dependency inference was performed.",
+ "diff_text": "diff --git a/settings.py b/settings.py\nindex 6a8ed7c..572bbe2 100644\n--- a/settings.py\n+++ b/settings.py\n@@ -1,4 +1,4 @@\n-LIMIT = 10\n+LIMIT = 7\n \n \n def accepts(amount):\n@@ -6,7 +6,7 @@ def accepts(amount):\n \n \n def ignored(amount):\n- return amount + 5\n+ return amount + 6\n \n \n class Policy:\n"
+ },
+ "ok": true
+}
\ No newline at end of file
diff --git a/docs/receiving/explicit-targets-a219f250962c/independent/native-run/report.md b/docs/receiving/explicit-targets-a219f250962c/independent/native-run/report.md
new file mode 100644
index 0000000..dfed87c
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/independent/native-run/report.md
@@ -0,0 +1,30 @@
+# TestPilot report: **passed**
+
+- Caller-selected functions: settings.py::Policy.cap, settings.py::accepts
+- Selection: Caller selected these targets; no dependency inference was performed.
+- Tests written: 2 in tests/test_selected.py
+- Repair rounds used: 1/1
+- Final run: 3 passed, 0 failed, 0 errors, 0 skipped (rc=0); generated: 2 passed of 2 collected in 0.736s
+- Coverage (total): 77.8% -> 100.0% (+22.2 pts)
+- Coverage (changed lines, n=0): None% -> None%
+- Tokens: 1199 (estimated); cost: unpriced (set TESTPILOT_PRICES)
+ - `nvidia/nemotron-3-super-120b-a12b`: 1 calls, 265 in / 15 out
+ - `nvidia/Nemotron-3_5-Lightning`: 2 calls, 811 in / 108 out
+
+## Patch
+
+```diff
+diff --git a/tests/test_selected.py b/tests/test_selected.py
+new file mode 100644
+--- /dev/null
++++ b/tests/test_selected.py
+@@ -0,0 +1,8 @@
++from settings import accepts, Policy
++
++def test_selected_limit():
++ assert accepts(7) is True
++ assert accepts(8) is False
++
++def test_selected_default():
++ assert Policy.cap(20) == 7
+```
diff --git a/docs/receiving/explicit-targets-a219f250962c/independent/native-run/testpilot.patch b/docs/receiving/explicit-targets-a219f250962c/independent/native-run/testpilot.patch
new file mode 100644
index 0000000..c0c1e6d
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/independent/native-run/testpilot.patch
@@ -0,0 +1,13 @@
+diff --git a/tests/test_selected.py b/tests/test_selected.py
+new file mode 100644
+--- /dev/null
++++ b/tests/test_selected.py
+@@ -0,0 +1,8 @@
++from settings import accepts, Policy
++
++def test_selected_limit():
++ assert accepts(7) is True
++ assert accepts(8) is False
++
++def test_selected_default():
++ assert Policy.cap(20) == 7
diff --git a/docs/receiving/explicit-targets-a219f250962c/independent/receive_explicit_targets.py b/docs/receiving/explicit-targets-a219f250962c/independent/receive_explicit_targets.py
new file mode 100644
index 0000000..e3fa40e
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/independent/receive_explicit_targets.py
@@ -0,0 +1,322 @@
+"""Independent native receiving for TestPilot's explicit-target contribution.
+
+The optional observation wrapper calls the actual CLI main and native
+make_client, returning its untouched ScriptedModel while retaining actual calls.
+No production file is edited, no model/provider transport is used.
+"""
+from __future__ import annotations
+
+import argparse
+import hashlib
+import json
+import os
+from pathlib import Path
+import shutil
+import subprocess
+import sys
+import tempfile
+import time
+import traceback
+
+BEFORE = """LIMIT = 10
+
+
+def accepts(amount):
+ return amount <= LIMIT
+
+
+def ignored(amount):
+ return amount + 5
+
+
+class Policy:
+ @staticmethod
+ def cap(amount, limit=LIMIT):
+ return min(amount, limit)
+"""
+AFTER = BEFORE.replace("LIMIT = 10", "LIMIT = 7").replace("amount + 5", "amount + 6")
+EXISTING = "from settings import ignored\n\ndef test_existing_stays_in_run():\n assert ignored(1) == 7\n"
+FIRST_TESTS = """from settings import accepts, Policy
+
+def test_selected_limit():
+ assert accepts(8) is True
+
+def test_selected_default():
+ assert Policy.cap(20) == 7
+"""
+FINAL_TESTS = """from settings import accepts, Policy
+
+def test_selected_limit():
+ assert accepts(7) is True
+ assert accepts(8) is False
+
+def test_selected_default():
+ assert Policy.cap(20) == 7
+"""
+OBSERVER = r"""
+import json, os, pathlib, sys
+from testpilot import __main__ as cli
+from testpilot.model import ScriptedModel
+actual = cli.make_client
+seen = []
+def record_client(*args, **kwargs):
+ client = actual(*args, **kwargs)
+ if not isinstance(client, ScriptedModel):
+ raise RuntimeError("Receiver admits only the actual ScriptedModel")
+ seen.append(client)
+ return client
+cli.make_client = record_client
+try:
+ code = cli.main(sys.argv[1:])
+finally:
+ pathlib.Path(os.environ["TARGET_RECEIVER_CALLS"]).write_text(
+ json.dumps([{"type": type(c).__name__, "calls": c.calls} for c in seen], ensure_ascii=True, indent=2) + "\n",
+ encoding="utf-8",
+ )
+raise SystemExit(code)
+"""
+
+def sha(data):
+ return hashlib.sha256(data).hexdigest()
+
+def snapshot(root):
+ result = {}
+ for p in sorted(root.rglob("*")):
+ if p.is_file() and not p.is_symlink() and ".git" not in p.relative_to(root).parts:
+ b = p.read_bytes()
+ result[str(p.relative_to(root))] = {"bytes": len(b), "sha256": sha(b)}
+ return result
+
+def main():
+ ap = argparse.ArgumentParser()
+ ap.add_argument("--source", required=True)
+ ap.add_argument("--baseline-source", required=True)
+ ap.add_argument("--out", required=True)
+ args = ap.parse_args()
+ source = Path(args.source).resolve()
+ baseline_source = Path(args.baseline_source).resolve()
+ out = Path(args.out).resolve()
+ if out.exists():
+ raise RuntimeError("Receiver output destination already exists")
+ free = shutil.disk_usage(out.parent).free
+ if free < 1024 ** 3:
+ raise RuntimeError(f"Receiving free-space floor not met: {free}")
+ out.mkdir()
+ package_before = snapshot(source / "testpilot")
+ baseline_before = snapshot(baseline_source / "testpilot")
+ receipt = {"schema": "testpilot.independent_explicit_targets.v1",
+ "started_at": time.time(), "python": sys.version,
+ "source": str(source), "baseline_source": str(baseline_source),
+ "free_bytes_before": free, "commands": [], "groups": [],
+ "qualification": "Native synthetic fixtures and actual ScriptedModel only; no live model quality claim"}
+ env = dict(os.environ)
+ env["PYTHONDONTWRITEBYTECODE"] = "1"
+ env["PYTEST_DISABLE_PLUGIN_AUTOLOAD"] = "1"
+ env["PYTHONPATH"] = str(source)
+ for key in list(env):
+ if key.startswith("TESTPILOT_"):
+ env.pop(key)
+ env["TESTPILOT_BACKEND"] = "scripted"
+
+ def command(label, argv, *, cwd=None, input_bytes=None, child_env=None, expected=0, timeout=45):
+ began = time.monotonic()
+ result = subprocess.run(argv, cwd=cwd, input=input_bytes, env=child_env or env,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=timeout)
+ record = {"label": label, "argv": [str(a) for a in argv],
+ "returncode": result.returncode, "seconds": time.monotonic() - began,
+ "stdout": result.stdout.decode("utf-8", "backslashreplace"),
+ "stderr": result.stderr.decode("utf-8", "backslashreplace")}
+ receipt["commands"].append(record)
+ if expected is not None and result.returncode != expected:
+ raise AssertionError(f"{label}: exit {result.returncode}, expected {expected}: {record['stderr']}")
+ return record
+
+ def cli(label, argv, *, source_root=source, observe=False, input_bytes=None, expected=0):
+ child_env = dict(env, PYTHONPATH=str(source_root))
+ calls = out / (label.replace("/", "_") + "-model-calls.json")
+ prefix = [sys.executable, "-m", "testpilot"]
+ if observe:
+ child_env["TARGET_RECEIVER_CALLS"] = str(calls)
+ prefix = [sys.executable, "-c", OBSERVER]
+ record = command(label, prefix + argv, child_env=child_env, input_bytes=input_bytes, expected=expected)
+ seen = json.loads(calls.read_text()) if calls.exists() else []
+ return record, seen
+
+ def group(name, run):
+ began = time.monotonic()
+ try:
+ details = run()
+ receipt["groups"].append({"name": name, "pass": True, "seconds": time.monotonic() - began, "details": details})
+ except Exception:
+ receipt["groups"].append({"name": name, "pass": False, "seconds": time.monotonic() - began, "traceback": traceback.format_exc()})
+ (out / "receiving-progress.json").write_text(json.dumps(receipt, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
+
+ try:
+ with tempfile.TemporaryDirectory(prefix="fixture-", dir=out) as raw:
+ fixture = Path(raw)
+ repo = fixture / "repo"
+ repo.mkdir()
+ (repo / "tests").mkdir()
+ (repo / "settings.py").write_text(BEFORE, encoding="utf-8")
+ (repo / "tests/test_existing.py").write_text(EXISTING, encoding="utf-8")
+ command("git-init", ["git", "init", "-q", str(repo)])
+ command("git-add", ["git", "-C", str(repo), "add", "."])
+ command("git-commit", ["git", "-C", str(repo), "-c", "user.name=HAMON receiving",
+ "-c", "user.email=hamon-receiving@invalid.example", "commit", "-qm", "Synthetic receiving baseline"])
+ (repo / "settings.py").write_text(AFTER, encoding="utf-8")
+ diff = command("actual-git-diff", ["git", "-C", str(repo), "diff", "HEAD", "--", "*.py"])["stdout"]
+ diff_path = fixture / "change.diff"
+ diff_path.write_text(diff, encoding="utf-8")
+ specs = ["settings.py::Policy.cap", "settings.py::accepts", "settings.py::Policy.cap"]
+ spec_args = [part for spec in specs for part in ("--target", spec)]
+ common = ["--repo", str(repo), "--git-base", "HEAD"]
+ original = snapshot(repo)
+ scripts = fixture / "script"
+ scripts.mkdir()
+ fence = chr(96) * 3
+ for name, content in [("00-plan.txt", "Pin only the caller-selected boundary and captured default."),
+ ("01-generate.txt", fence + "python path=tests/test_selected.py\n" + FIRST_TESTS + fence + "\n"),
+ ("02-repair.txt", fence + "python path=tests/test_selected.py\n" + FINAL_TESTS + fence + "\n")]:
+ (scripts / name).write_text(content, encoding="utf-8")
+
+ def default_parity():
+ old, _ = cli("default-baseline", ["targets"] + common + ["--json"], source_root=baseline_source)
+ new, _ = cli("default-candidate", ["targets"] + common + ["--json"])
+ assert old["stdout"] == new["stdout"]
+ obj = json.loads(new["stdout"])
+ assert list(obj) == ["changed_functions"]
+ assert [f["qualname"] for f in obj["changed_functions"]] == ["ignored"]
+ return {"exact_json_equal": True, "default_targets": ["ignored"]}
+
+ def preview():
+ record, _ = cli("explicit-preview", ["targets"] + common + spec_args + ["--json"])
+ data = json.loads(record["stdout"])
+ functions = data["changed_functions"]
+ assert [f["qualname"] for f in functions] == ["Policy.cap", "accepts"]
+ assert all(f["changed_lines"] == [] for f in functions)
+ selection = data["selection"]
+ assert selection["mode"] == "explicit" and selection["requested"] == specs
+ assert selection["diff_text"] == diff
+ assert "Caller" in selection["reason"] and "inference" in selection["reason"]
+ assert functions[0]["is_method"] is True and functions[1]["is_method"] is False
+ assert snapshot(repo) == original
+ receipt["preview"] = data
+ return {"caller_order": True, "duplicates_once": True, "unchanged_lines_empty": True,
+ "input_unchanged": True}
+
+ def generation():
+ output = out / "native-run"
+ record, clients = cli("explicit-run", ["run"] + common + spec_args +
+ ["--backend", "scripted", "--script", str(scripts),
+ "--rounds", "1", "--timeout", "15", "--out", str(output)], observe=True)
+ data = json.loads((output / "report.json").read_text(encoding="utf-8"))
+ assert data["status"] == "passed" and data["tests_written"] == 2
+ assert data["repair_rounds_used"] == 1
+ assert [f["qualname"] for f in data["changed_functions"]] == ["Policy.cap", "accepts"]
+ assert data["selection"]["requested"] == specs and data["selection"]["diff_text"] == diff
+ assert len(clients) == 1 and clients[0]["type"] == "ScriptedModel"
+ calls = clients[0]["calls"]
+ assert len(calls) == 3
+ for _, messages in calls:
+ text = "\n".join(m["content"] for m in messages)
+ assert diff in text
+ for fn in data["changed_functions"]:
+ assert fn["source"] in text
+ assert "Caller" in text or "caller" in text
+ coverage = data.get("coverage")
+ if coverage is not None:
+ assert coverage["changed_lines_executable"] == 0
+ assert coverage["changed_lines_before"] is None and coverage["changed_lines_after"] is None
+ assert snapshot(repo) == original
+ receipt["native_result"] = data
+ return {"actual_cli_main": True, "observation_only_wrapper": True, "native_model_calls": 3,
+ "all_prompts_contain_exact_diff_and_selected_source": True,
+ "existing_and_generated_suite_passed": True, "source_unchanged": True}
+
+ def patch_receive():
+ output = out / "native-run"
+ patch = output / "testpilot.patch"
+ assert patch.is_file()
+ delivery = fixture / "delivery"
+ command("delivery-clone", ["git", "clone", "-q", "--no-hardlinks", str(repo), str(delivery)])
+ (delivery / "settings.py").write_text(AFTER, encoding="utf-8")
+ before_input = (delivery / "settings.py").read_bytes()
+ existing_input = (delivery / "tests/test_existing.py").read_bytes()
+ command("patch-check", ["git", "-C", str(delivery), "apply", "--check", str(patch)])
+ command("patch-apply", ["git", "-C", str(delivery), "apply", str(patch)])
+ ran = command("installed-patch-pytest", [sys.executable, "-m", "pytest", "-q", "-p", "no:cacheprovider"],
+ cwd=delivery)
+ assert "3 passed" in ran["stdout"]
+ assert (delivery / "settings.py").read_bytes() == before_input
+ assert (delivery / "tests/test_existing.py").read_bytes() == existing_input
+ return {"patch_sha256": sha(patch.read_bytes()), "actual_git_apply": True,
+ "installed_tests": 3, "original_inputs_preserved": True}
+
+ group("default_selector_output_parity", default_parity)
+ group("explicit_preview_custody_and_order", preview)
+ group("actual_cli_generation_repair_and_prompt_context", generation)
+ group("actual_patch_install_and_native_execution", patch_receive)
+
+ (repo / "ambiguous.py").write_text("if True:\n def same():\n return 1\nelse:\n def same():\n return 2\n", encoding="utf-8")
+ (repo / "badsyntax.py").write_text("def broken(:\n pass\n", encoding="utf-8")
+ (repo / "plain.txt").write_text("def hidden():\n return 1\n", encoding="utf-8")
+ outside = fixture / "outside.py"
+ outside.write_text("def hidden():\n return 'outside'\n", encoding="utf-8")
+ (repo / "outside_link.py").symlink_to(outside)
+ refusals = ["settings.py::missing", "ambiguous.py::same", "tests/test_existing.py::test_existing_stays_in_run",
+ "../outside.py::hidden", str(outside) + "::hidden", "outside_link.py::hidden",
+ "plain.txt::hidden", "badsyntax.py::broken", "settings.py", "settings.py::"]
+ refusal_snapshot = snapshot(repo)
+ def refused_targets():
+ cases = []
+ for i, target in enumerate(refusals):
+ output = out / f"refused-output-{i}"
+ record, clients = cli(f"refusal-{i}", ["run", "--repo", str(repo), "--diff", str(diff_path),
+ "--target", target, "--backend", "scripted",
+ "--script", str(scripts), "--out", str(output)],
+ observe=True, expected=2)
+ assert not output.exists()
+ assert not any(c["calls"] for c in clients)
+ cases.append({"target": target, "exit": record["returncode"], "model_calls": 0, "output_absent": True})
+ assert snapshot(repo) == refusal_snapshot
+ assert outside.read_text() == "def hidden():\n return 'outside'\n"
+ return {"cases": cases, "source_and_outside_bytes_unchanged": True}
+ group("refusals_before_execution_or_generation", refused_targets)
+
+ # Remove only our intentionally invalid sibling so it cannot interfere
+ # with current native diff selection during this separate preview.
+ (repo / "badsyntax.py").unlink()
+ (repo / "latin1.py").write_bytes(b"# coding: latin-1\n# caf\xe9\n\ndef caf\xe9(value):\n return value + 1\n")
+ (repo / "never_import.py").write_text("raise RuntimeError('preview must never import this source')\n\ndef picked(value):\n return value\n", encoding="utf-8")
+ def literal_and_read_only_preview():
+ record, _ = cli("literal-preview", ["targets", "--repo", str(repo), "--diff", "-",
+ "--target", "latin1.py::caf\u00e9", "--target", "never_import.py::picked", "--json"],
+ input_bytes=diff.encode("utf-8"))
+ data = json.loads(record["stdout"])
+ assert [f["qualname"] for f in data["changed_functions"]] == ["caf\u00e9", "picked"]
+ assert "def caf\u00e9(value):" in data["changed_functions"][0]["source"]
+ assert data["selection"]["diff_text"] == diff
+ assert (repo / "latin1.py").read_bytes().startswith(b"# coding: latin-1\n# caf\xe9")
+ return {"unicode_name_and_encoding_cookie": True, "stdin_context_exact": True,
+ "selected_module_not_imported": True}
+ group("encoded_source_and_read_only_stdin_preview", literal_and_read_only_preview)
+ except Exception:
+ receipt["fatal_error"] = traceback.format_exc()
+ finally:
+ receipt["package_source_unchanged"] = snapshot(source / "testpilot") == package_before
+ receipt["baseline_source_unchanged"] = snapshot(baseline_source / "testpilot") == baseline_before
+ receipt["source_fingerprints"] = package_before
+ receipt["finished_at"] = time.time()
+ receipt["accepted"] = (len(receipt["groups"]) == 6 and all(g["pass"] for g in receipt["groups"])
+ and receipt["package_source_unchanged"] and receipt["baseline_source_unchanged"]
+ and "fatal_error" not in receipt)
+ data = (json.dumps(receipt, indent=2, ensure_ascii=True) + "\n").encode()
+ (out / "receiving-result.json").write_bytes(data)
+ print(json.dumps({"schema": receipt["schema"], "accepted": receipt["accepted"],
+ "groups": [{"name": g["name"], "pass": g["pass"]} for g in receipt["groups"]],
+ "commands": len(receipt["commands"]), "receipt": str(out / "receiving-result.json"),
+ "bytes": len(data), "sha256": sha(data)}))
+ return 0 if receipt["accepted"] else 1
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/docs/receiving/explicit-targets-a219f250962c/independent/receiving-result.json b/docs/receiving/explicit-targets-a219f250962c/independent/receiving-result.json
new file mode 100644
index 0000000..5d0e715
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/independent/receiving-result.json
@@ -0,0 +1,1017 @@
+{
+ "schema": "testpilot.independent_explicit_targets.v1",
+ "started_at": 1791472093.252716,
+ "python": "3.13.7 (v3.13.7:bcee1c32211, Aug 14 2025, 19:10:51) [Clang 16.0.0 (clang-1600.0.26.6)]",
+ "source": "/Users/me/hamon-testpilot-explicit-targets-a219f250962c",
+ "baseline_source": "/Users/me/hamon-testpilot-target-assessment-a219f250962c/current/snapshot",
+ "free_bytes_before": 2145505280,
+ "commands": [
+ {
+ "label": "git-init",
+ "argv": [
+ "git",
+ "init",
+ "-q",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/repo"
+ ],
+ "returncode": 0,
+ "seconds": 0.03770787501707673,
+ "stdout": "",
+ "stderr": ""
+ },
+ {
+ "label": "git-add",
+ "argv": [
+ "git",
+ "-C",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/repo",
+ "add",
+ "."
+ ],
+ "returncode": 0,
+ "seconds": 0.029113124939613044,
+ "stdout": "",
+ "stderr": ""
+ },
+ {
+ "label": "git-commit",
+ "argv": [
+ "git",
+ "-C",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/repo",
+ "-c",
+ "user.name=HAMON receiving",
+ "-c",
+ "user.email=hamon-receiving@invalid.example",
+ "commit",
+ "-qm",
+ "Synthetic receiving baseline"
+ ],
+ "returncode": 0,
+ "seconds": 0.043260916019789875,
+ "stdout": "",
+ "stderr": ""
+ },
+ {
+ "label": "actual-git-diff",
+ "argv": [
+ "git",
+ "-C",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/repo",
+ "diff",
+ "HEAD",
+ "--",
+ "*.py"
+ ],
+ "returncode": 0,
+ "seconds": 0.019571332959458232,
+ "stdout": "diff --git a/settings.py b/settings.py\nindex 6a8ed7c..572bbe2 100644\n--- a/settings.py\n+++ b/settings.py\n@@ -1,4 +1,4 @@\n-LIMIT = 10\n+LIMIT = 7\n \n \n def accepts(amount):\n@@ -6,7 +6,7 @@ def accepts(amount):\n \n \n def ignored(amount):\n- return amount + 5\n+ return amount + 6\n \n \n class Policy:\n",
+ "stderr": ""
+ },
+ {
+ "label": "default-baseline",
+ "argv": [
+ "/Users/me/testpilot-html-066deeadcc8b/venv/bin/python",
+ "-m",
+ "testpilot",
+ "targets",
+ "--repo",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/repo",
+ "--git-base",
+ "HEAD",
+ "--json"
+ ],
+ "returncode": 0,
+ "seconds": 0.19371374999172986,
+ "stdout": "{\n \"changed_functions\": [\n {\n \"path\": \"settings.py\",\n \"module\": \"settings\",\n \"qualname\": \"ignored\",\n \"lineno\": 8,\n \"end_lineno\": 9,\n \"changed_lines\": [\n 9\n ],\n \"source\": \"def ignored(amount):\\n return amount + 6\",\n \"is_method\": false\n }\n ]\n}\n",
+ "stderr": ""
+ },
+ {
+ "label": "default-candidate",
+ "argv": [
+ "/Users/me/testpilot-html-066deeadcc8b/venv/bin/python",
+ "-m",
+ "testpilot",
+ "targets",
+ "--repo",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/repo",
+ "--git-base",
+ "HEAD",
+ "--json"
+ ],
+ "returncode": 0,
+ "seconds": 0.17557308299001306,
+ "stdout": "{\n \"changed_functions\": [\n {\n \"path\": \"settings.py\",\n \"module\": \"settings\",\n \"qualname\": \"ignored\",\n \"lineno\": 8,\n \"end_lineno\": 9,\n \"changed_lines\": [\n 9\n ],\n \"source\": \"def ignored(amount):\\n return amount + 6\",\n \"is_method\": false\n }\n ]\n}\n",
+ "stderr": ""
+ },
+ {
+ "label": "explicit-preview",
+ "argv": [
+ "/Users/me/testpilot-html-066deeadcc8b/venv/bin/python",
+ "-m",
+ "testpilot",
+ "targets",
+ "--repo",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/repo",
+ "--git-base",
+ "HEAD",
+ "--target",
+ "settings.py::Policy.cap",
+ "--target",
+ "settings.py::accepts",
+ "--target",
+ "settings.py::Policy.cap",
+ "--json"
+ ],
+ "returncode": 0,
+ "seconds": 0.17145483393687755,
+ "stdout": "{\n \"changed_functions\": [\n {\n \"path\": \"settings.py\",\n \"module\": \"settings\",\n \"qualname\": \"Policy.cap\",\n \"lineno\": 13,\n \"end_lineno\": 15,\n \"changed_lines\": [],\n \"source\": \" @staticmethod\\n def cap(amount, limit=LIMIT):\\n return min(amount, limit)\",\n \"is_method\": true\n },\n {\n \"path\": \"settings.py\",\n \"module\": \"settings\",\n \"qualname\": \"accepts\",\n \"lineno\": 4,\n \"end_lineno\": 5,\n \"changed_lines\": [],\n \"source\": \"def accepts(amount):\\n return amount <= LIMIT\",\n \"is_method\": false\n }\n ],\n \"selection\": {\n \"mode\": \"explicit\",\n \"requested\": [\n \"settings.py::Policy.cap\",\n \"settings.py::accepts\",\n \"settings.py::Policy.cap\"\n ],\n \"reason\": \"Caller selected these targets; no dependency inference was performed.\",\n \"diff_text\": \"diff --git a/settings.py b/settings.py\\nindex 6a8ed7c..572bbe2 100644\\n--- a/settings.py\\n+++ b/settings.py\\n@@ -1,4 +1,4 @@\\n-LIMIT = 10\\n+LIMIT = 7\\n \\n \\n def accepts(amount):\\n@@ -6,7 +6,7 @@ def accepts(amount):\\n \\n \\n def ignored(amount):\\n- return amount + 5\\n+ return amount + 6\\n \\n \\n class Policy:\\n\"\n }\n}\n",
+ "stderr": ""
+ },
+ {
+ "label": "explicit-run",
+ "argv": [
+ "/Users/me/testpilot-html-066deeadcc8b/venv/bin/python",
+ "-c",
+ "\nimport json, os, pathlib, sys\nfrom testpilot import __main__ as cli\nfrom testpilot.model import ScriptedModel\nactual = cli.make_client\nseen = []\ndef record_client(*args, **kwargs):\n client = actual(*args, **kwargs)\n if not isinstance(client, ScriptedModel):\n raise RuntimeError(\"Receiver admits only the actual ScriptedModel\")\n seen.append(client)\n return client\ncli.make_client = record_client\ntry:\n code = cli.main(sys.argv[1:])\nfinally:\n pathlib.Path(os.environ[\"TARGET_RECEIVER_CALLS\"]).write_text(\n json.dumps([{\"type\": type(c).__name__, \"calls\": c.calls} for c in seen], ensure_ascii=True, indent=2) + \"\\n\",\n encoding=\"utf-8\",\n )\nraise SystemExit(code)\n",
+ "run",
+ "--repo",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/repo",
+ "--git-base",
+ "HEAD",
+ "--target",
+ "settings.py::Policy.cap",
+ "--target",
+ "settings.py::accepts",
+ "--target",
+ "settings.py::Policy.cap",
+ "--backend",
+ "scripted",
+ "--script",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/script",
+ "--rounds",
+ "1",
+ "--timeout",
+ "15",
+ "--out",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/native-run"
+ ],
+ "returncode": 0,
+ "seconds": 3.177388541982509,
+ "stdout": "# TestPilot report: **passed**\n\n- Caller-selected functions: settings.py::Policy.cap, settings.py::accepts\n- Selection: Caller selected these targets; no dependency inference was performed.\n- Tests written: 2 in tests/test_selected.py\n- Repair rounds used: 1/1\n- Final run: 3 passed, 0 failed, 0 errors, 0 skipped (rc=0); generated: 2 passed of 2 collected in 0.736s\n- Coverage (total): 77.8% -> 100.0% (+22.2 pts)\n- Coverage (changed lines, n=0): None% -> None%\n- Tokens: 1199 (estimated); cost: unpriced (set TESTPILOT_PRICES)\n - `nvidia/nemotron-3-super-120b-a12b`: 1 calls, 265 in / 15 out\n - `nvidia/Nemotron-3_5-Lightning`: 2 calls, 811 in / 108 out\n\nwrote /Users/me/testpilot-explicit-target-review-a219f250962c/run-01/native-run/testpilot.patch, /Users/me/testpilot-explicit-target-review-a219f250962c/run-01/native-run/report.json, /Users/me/testpilot-explicit-target-review-a219f250962c/run-01/native-run/report.md, /Users/me/testpilot-explicit-target-review-a219f250962c/run-01/native-run/report.html\n",
+ "stderr": ""
+ },
+ {
+ "label": "delivery-clone",
+ "argv": [
+ "git",
+ "clone",
+ "-q",
+ "--no-hardlinks",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/repo",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/delivery"
+ ],
+ "returncode": 0,
+ "seconds": 0.06227783299982548,
+ "stdout": "",
+ "stderr": ""
+ },
+ {
+ "label": "patch-check",
+ "argv": [
+ "git",
+ "-C",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/delivery",
+ "apply",
+ "--check",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/native-run/testpilot.patch"
+ ],
+ "returncode": 0,
+ "seconds": 0.02265800000168383,
+ "stdout": "",
+ "stderr": ""
+ },
+ {
+ "label": "patch-apply",
+ "argv": [
+ "git",
+ "-C",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/delivery",
+ "apply",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/native-run/testpilot.patch"
+ ],
+ "returncode": 0,
+ "seconds": 0.03403249999973923,
+ "stdout": "",
+ "stderr": ""
+ },
+ {
+ "label": "installed-patch-pytest",
+ "argv": [
+ "/Users/me/testpilot-html-066deeadcc8b/venv/bin/python",
+ "-m",
+ "pytest",
+ "-q",
+ "-p",
+ "no:cacheprovider"
+ ],
+ "returncode": 0,
+ "seconds": 0.20384329196531326,
+ "stdout": "... [100%]\n3 passed in 0.00s\n",
+ "stderr": ""
+ },
+ {
+ "label": "refusal-0",
+ "argv": [
+ "/Users/me/testpilot-html-066deeadcc8b/venv/bin/python",
+ "-c",
+ "\nimport json, os, pathlib, sys\nfrom testpilot import __main__ as cli\nfrom testpilot.model import ScriptedModel\nactual = cli.make_client\nseen = []\ndef record_client(*args, **kwargs):\n client = actual(*args, **kwargs)\n if not isinstance(client, ScriptedModel):\n raise RuntimeError(\"Receiver admits only the actual ScriptedModel\")\n seen.append(client)\n return client\ncli.make_client = record_client\ntry:\n code = cli.main(sys.argv[1:])\nfinally:\n pathlib.Path(os.environ[\"TARGET_RECEIVER_CALLS\"]).write_text(\n json.dumps([{\"type\": type(c).__name__, \"calls\": c.calls} for c in seen], ensure_ascii=True, indent=2) + \"\\n\",\n encoding=\"utf-8\",\n )\nraise SystemExit(code)\n",
+ "run",
+ "--repo",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/repo",
+ "--diff",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/change.diff",
+ "--target",
+ "settings.py::missing",
+ "--backend",
+ "scripted",
+ "--script",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/script",
+ "--out",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/refused-output-0"
+ ],
+ "returncode": 2,
+ "seconds": 0.1339709999738261,
+ "stdout": "",
+ "stderr": "testpilot: invalid target selection: no selectable function matches 'settings.py::missing'\n"
+ },
+ {
+ "label": "refusal-1",
+ "argv": [
+ "/Users/me/testpilot-html-066deeadcc8b/venv/bin/python",
+ "-c",
+ "\nimport json, os, pathlib, sys\nfrom testpilot import __main__ as cli\nfrom testpilot.model import ScriptedModel\nactual = cli.make_client\nseen = []\ndef record_client(*args, **kwargs):\n client = actual(*args, **kwargs)\n if not isinstance(client, ScriptedModel):\n raise RuntimeError(\"Receiver admits only the actual ScriptedModel\")\n seen.append(client)\n return client\ncli.make_client = record_client\ntry:\n code = cli.main(sys.argv[1:])\nfinally:\n pathlib.Path(os.environ[\"TARGET_RECEIVER_CALLS\"]).write_text(\n json.dumps([{\"type\": type(c).__name__, \"calls\": c.calls} for c in seen], ensure_ascii=True, indent=2) + \"\\n\",\n encoding=\"utf-8\",\n )\nraise SystemExit(code)\n",
+ "run",
+ "--repo",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/repo",
+ "--diff",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/change.diff",
+ "--target",
+ "ambiguous.py::same",
+ "--backend",
+ "scripted",
+ "--script",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/script",
+ "--out",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/refused-output-1"
+ ],
+ "returncode": 2,
+ "seconds": 0.12878104101400822,
+ "stdout": "",
+ "stderr": "testpilot: invalid target selection: ambiguous target matches 2 definitions: 'ambiguous.py::same'\n"
+ },
+ {
+ "label": "refusal-2",
+ "argv": [
+ "/Users/me/testpilot-html-066deeadcc8b/venv/bin/python",
+ "-c",
+ "\nimport json, os, pathlib, sys\nfrom testpilot import __main__ as cli\nfrom testpilot.model import ScriptedModel\nactual = cli.make_client\nseen = []\ndef record_client(*args, **kwargs):\n client = actual(*args, **kwargs)\n if not isinstance(client, ScriptedModel):\n raise RuntimeError(\"Receiver admits only the actual ScriptedModel\")\n seen.append(client)\n return client\ncli.make_client = record_client\ntry:\n code = cli.main(sys.argv[1:])\nfinally:\n pathlib.Path(os.environ[\"TARGET_RECEIVER_CALLS\"]).write_text(\n json.dumps([{\"type\": type(c).__name__, \"calls\": c.calls} for c in seen], ensure_ascii=True, indent=2) + \"\\n\",\n encoding=\"utf-8\",\n )\nraise SystemExit(code)\n",
+ "run",
+ "--repo",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/repo",
+ "--diff",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/change.diff",
+ "--target",
+ "tests/test_existing.py::test_existing_stays_in_run",
+ "--backend",
+ "scripted",
+ "--script",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/script",
+ "--out",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/refused-output-2"
+ ],
+ "returncode": 2,
+ "seconds": 0.13953220890834928,
+ "stdout": "",
+ "stderr": "testpilot: invalid target selection: target must be a Python source file outside tests: 'tests/test_existing.py::test_existing_stays_in_run'\n"
+ },
+ {
+ "label": "refusal-3",
+ "argv": [
+ "/Users/me/testpilot-html-066deeadcc8b/venv/bin/python",
+ "-c",
+ "\nimport json, os, pathlib, sys\nfrom testpilot import __main__ as cli\nfrom testpilot.model import ScriptedModel\nactual = cli.make_client\nseen = []\ndef record_client(*args, **kwargs):\n client = actual(*args, **kwargs)\n if not isinstance(client, ScriptedModel):\n raise RuntimeError(\"Receiver admits only the actual ScriptedModel\")\n seen.append(client)\n return client\ncli.make_client = record_client\ntry:\n code = cli.main(sys.argv[1:])\nfinally:\n pathlib.Path(os.environ[\"TARGET_RECEIVER_CALLS\"]).write_text(\n json.dumps([{\"type\": type(c).__name__, \"calls\": c.calls} for c in seen], ensure_ascii=True, indent=2) + \"\\n\",\n encoding=\"utf-8\",\n )\nraise SystemExit(code)\n",
+ "run",
+ "--repo",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/repo",
+ "--diff",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/change.diff",
+ "--target",
+ "../outside.py::hidden",
+ "--backend",
+ "scripted",
+ "--script",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/script",
+ "--out",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/refused-output-3"
+ ],
+ "returncode": 2,
+ "seconds": 0.14612337504513562,
+ "stdout": "",
+ "stderr": "testpilot: invalid target selection: cannot inspect target '../outside.py::hidden': diff source must be repository-relative: '../outside.py'\n"
+ },
+ {
+ "label": "refusal-4",
+ "argv": [
+ "/Users/me/testpilot-html-066deeadcc8b/venv/bin/python",
+ "-c",
+ "\nimport json, os, pathlib, sys\nfrom testpilot import __main__ as cli\nfrom testpilot.model import ScriptedModel\nactual = cli.make_client\nseen = []\ndef record_client(*args, **kwargs):\n client = actual(*args, **kwargs)\n if not isinstance(client, ScriptedModel):\n raise RuntimeError(\"Receiver admits only the actual ScriptedModel\")\n seen.append(client)\n return client\ncli.make_client = record_client\ntry:\n code = cli.main(sys.argv[1:])\nfinally:\n pathlib.Path(os.environ[\"TARGET_RECEIVER_CALLS\"]).write_text(\n json.dumps([{\"type\": type(c).__name__, \"calls\": c.calls} for c in seen], ensure_ascii=True, indent=2) + \"\\n\",\n encoding=\"utf-8\",\n )\nraise SystemExit(code)\n",
+ "run",
+ "--repo",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/repo",
+ "--diff",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/change.diff",
+ "--target",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/outside.py::hidden",
+ "--backend",
+ "scripted",
+ "--script",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/script",
+ "--out",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/refused-output-4"
+ ],
+ "returncode": 2,
+ "seconds": 0.14638550009112805,
+ "stdout": "",
+ "stderr": "testpilot: invalid target selection: cannot inspect target '/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/outside.py::hidden': diff source must be repository-relative: '/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/outside.py'\n"
+ },
+ {
+ "label": "refusal-5",
+ "argv": [
+ "/Users/me/testpilot-html-066deeadcc8b/venv/bin/python",
+ "-c",
+ "\nimport json, os, pathlib, sys\nfrom testpilot import __main__ as cli\nfrom testpilot.model import ScriptedModel\nactual = cli.make_client\nseen = []\ndef record_client(*args, **kwargs):\n client = actual(*args, **kwargs)\n if not isinstance(client, ScriptedModel):\n raise RuntimeError(\"Receiver admits only the actual ScriptedModel\")\n seen.append(client)\n return client\ncli.make_client = record_client\ntry:\n code = cli.main(sys.argv[1:])\nfinally:\n pathlib.Path(os.environ[\"TARGET_RECEIVER_CALLS\"]).write_text(\n json.dumps([{\"type\": type(c).__name__, \"calls\": c.calls} for c in seen], ensure_ascii=True, indent=2) + \"\\n\",\n encoding=\"utf-8\",\n )\nraise SystemExit(code)\n",
+ "run",
+ "--repo",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/repo",
+ "--diff",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/change.diff",
+ "--target",
+ "outside_link.py::hidden",
+ "--backend",
+ "scripted",
+ "--script",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/script",
+ "--out",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/refused-output-5"
+ ],
+ "returncode": 2,
+ "seconds": 0.14991300005931407,
+ "stdout": "",
+ "stderr": "testpilot: invalid target selection: cannot inspect target 'outside_link.py::hidden': diff source resolves outside repository: 'outside_link.py'\n"
+ },
+ {
+ "label": "refusal-6",
+ "argv": [
+ "/Users/me/testpilot-html-066deeadcc8b/venv/bin/python",
+ "-c",
+ "\nimport json, os, pathlib, sys\nfrom testpilot import __main__ as cli\nfrom testpilot.model import ScriptedModel\nactual = cli.make_client\nseen = []\ndef record_client(*args, **kwargs):\n client = actual(*args, **kwargs)\n if not isinstance(client, ScriptedModel):\n raise RuntimeError(\"Receiver admits only the actual ScriptedModel\")\n seen.append(client)\n return client\ncli.make_client = record_client\ntry:\n code = cli.main(sys.argv[1:])\nfinally:\n pathlib.Path(os.environ[\"TARGET_RECEIVER_CALLS\"]).write_text(\n json.dumps([{\"type\": type(c).__name__, \"calls\": c.calls} for c in seen], ensure_ascii=True, indent=2) + \"\\n\",\n encoding=\"utf-8\",\n )\nraise SystemExit(code)\n",
+ "run",
+ "--repo",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/repo",
+ "--diff",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/change.diff",
+ "--target",
+ "plain.txt::hidden",
+ "--backend",
+ "scripted",
+ "--script",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/script",
+ "--out",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/refused-output-6"
+ ],
+ "returncode": 2,
+ "seconds": 0.1349534159526229,
+ "stdout": "",
+ "stderr": "testpilot: invalid target selection: target must be a Python source file outside tests: 'plain.txt::hidden'\n"
+ },
+ {
+ "label": "refusal-7",
+ "argv": [
+ "/Users/me/testpilot-html-066deeadcc8b/venv/bin/python",
+ "-c",
+ "\nimport json, os, pathlib, sys\nfrom testpilot import __main__ as cli\nfrom testpilot.model import ScriptedModel\nactual = cli.make_client\nseen = []\ndef record_client(*args, **kwargs):\n client = actual(*args, **kwargs)\n if not isinstance(client, ScriptedModel):\n raise RuntimeError(\"Receiver admits only the actual ScriptedModel\")\n seen.append(client)\n return client\ncli.make_client = record_client\ntry:\n code = cli.main(sys.argv[1:])\nfinally:\n pathlib.Path(os.environ[\"TARGET_RECEIVER_CALLS\"]).write_text(\n json.dumps([{\"type\": type(c).__name__, \"calls\": c.calls} for c in seen], ensure_ascii=True, indent=2) + \"\\n\",\n encoding=\"utf-8\",\n )\nraise SystemExit(code)\n",
+ "run",
+ "--repo",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/repo",
+ "--diff",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/change.diff",
+ "--target",
+ "badsyntax.py::broken",
+ "--backend",
+ "scripted",
+ "--script",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/script",
+ "--out",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/refused-output-7"
+ ],
+ "returncode": 2,
+ "seconds": 0.13287641596980393,
+ "stdout": "",
+ "stderr": "testpilot: invalid target selection: cannot inspect target 'badsyntax.py::broken': invalid syntax (badsyntax.py, line 1)\n"
+ },
+ {
+ "label": "refusal-8",
+ "argv": [
+ "/Users/me/testpilot-html-066deeadcc8b/venv/bin/python",
+ "-c",
+ "\nimport json, os, pathlib, sys\nfrom testpilot import __main__ as cli\nfrom testpilot.model import ScriptedModel\nactual = cli.make_client\nseen = []\ndef record_client(*args, **kwargs):\n client = actual(*args, **kwargs)\n if not isinstance(client, ScriptedModel):\n raise RuntimeError(\"Receiver admits only the actual ScriptedModel\")\n seen.append(client)\n return client\ncli.make_client = record_client\ntry:\n code = cli.main(sys.argv[1:])\nfinally:\n pathlib.Path(os.environ[\"TARGET_RECEIVER_CALLS\"]).write_text(\n json.dumps([{\"type\": type(c).__name__, \"calls\": c.calls} for c in seen], ensure_ascii=True, indent=2) + \"\\n\",\n encoding=\"utf-8\",\n )\nraise SystemExit(code)\n",
+ "run",
+ "--repo",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/repo",
+ "--diff",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/change.diff",
+ "--target",
+ "settings.py",
+ "--backend",
+ "scripted",
+ "--script",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/script",
+ "--out",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/refused-output-8"
+ ],
+ "returncode": 2,
+ "seconds": 0.1424293330637738,
+ "stdout": "",
+ "stderr": "testpilot: invalid target selection: expected path.py::qualname: 'settings.py'\n"
+ },
+ {
+ "label": "refusal-9",
+ "argv": [
+ "/Users/me/testpilot-html-066deeadcc8b/venv/bin/python",
+ "-c",
+ "\nimport json, os, pathlib, sys\nfrom testpilot import __main__ as cli\nfrom testpilot.model import ScriptedModel\nactual = cli.make_client\nseen = []\ndef record_client(*args, **kwargs):\n client = actual(*args, **kwargs)\n if not isinstance(client, ScriptedModel):\n raise RuntimeError(\"Receiver admits only the actual ScriptedModel\")\n seen.append(client)\n return client\ncli.make_client = record_client\ntry:\n code = cli.main(sys.argv[1:])\nfinally:\n pathlib.Path(os.environ[\"TARGET_RECEIVER_CALLS\"]).write_text(\n json.dumps([{\"type\": type(c).__name__, \"calls\": c.calls} for c in seen], ensure_ascii=True, indent=2) + \"\\n\",\n encoding=\"utf-8\",\n )\nraise SystemExit(code)\n",
+ "run",
+ "--repo",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/repo",
+ "--diff",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/change.diff",
+ "--target",
+ "settings.py::",
+ "--backend",
+ "scripted",
+ "--script",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/script",
+ "--out",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/refused-output-9"
+ ],
+ "returncode": 2,
+ "seconds": 0.14394554099999368,
+ "stdout": "",
+ "stderr": "testpilot: invalid target selection: expected path.py::qualname: 'settings.py::'\n"
+ },
+ {
+ "label": "literal-preview",
+ "argv": [
+ "/Users/me/testpilot-html-066deeadcc8b/venv/bin/python",
+ "-m",
+ "testpilot",
+ "targets",
+ "--repo",
+ "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/repo",
+ "--diff",
+ "-",
+ "--target",
+ "latin1.py::caf\u00e9",
+ "--target",
+ "never_import.py::picked",
+ "--json"
+ ],
+ "returncode": 0,
+ "seconds": 0.1541122089838609,
+ "stdout": "{\n \"changed_functions\": [\n {\n \"path\": \"latin1.py\",\n \"module\": \"latin1\",\n \"qualname\": \"caf\\u00e9\",\n \"lineno\": 4,\n \"end_lineno\": 5,\n \"changed_lines\": [],\n \"source\": \"def caf\\u00e9(value):\\n return value + 1\",\n \"is_method\": false\n },\n {\n \"path\": \"never_import.py\",\n \"module\": \"never_import\",\n \"qualname\": \"picked\",\n \"lineno\": 3,\n \"end_lineno\": 4,\n \"changed_lines\": [],\n \"source\": \"def picked(value):\\n return value\",\n \"is_method\": false\n }\n ],\n \"selection\": {\n \"mode\": \"explicit\",\n \"requested\": [\n \"latin1.py::caf\\u00e9\",\n \"never_import.py::picked\"\n ],\n \"reason\": \"Caller selected these targets; no dependency inference was performed.\",\n \"diff_text\": \"diff --git a/settings.py b/settings.py\\nindex 6a8ed7c..572bbe2 100644\\n--- a/settings.py\\n+++ b/settings.py\\n@@ -1,4 +1,4 @@\\n-LIMIT = 10\\n+LIMIT = 7\\n \\n \\n def accepts(amount):\\n@@ -6,7 +6,7 @@ def accepts(amount):\\n \\n \\n def ignored(amount):\\n- return amount + 5\\n+ return amount + 6\\n \\n \\n class Policy:\\n\"\n }\n}\n",
+ "stderr": ""
+ }
+ ],
+ "groups": [
+ {
+ "name": "default_selector_output_parity",
+ "pass": true,
+ "seconds": 0.36961191706359386,
+ "details": {
+ "exact_json_equal": true,
+ "default_targets": [
+ "ignored"
+ ]
+ }
+ },
+ {
+ "name": "explicit_preview_custody_and_order",
+ "pass": true,
+ "seconds": 0.1731045839842409,
+ "details": {
+ "caller_order": true,
+ "duplicates_once": true,
+ "unchanged_lines_empty": true,
+ "input_unchanged": true
+ }
+ },
+ {
+ "name": "actual_cli_generation_repair_and_prompt_context",
+ "pass": true,
+ "seconds": 3.1796726669417694,
+ "details": {
+ "actual_cli_main": true,
+ "observation_only_wrapper": true,
+ "native_model_calls": 3,
+ "all_prompts_contain_exact_diff_and_selected_source": true,
+ "existing_and_generated_suite_passed": true,
+ "source_unchanged": true
+ }
+ },
+ {
+ "name": "actual_patch_install_and_native_execution",
+ "pass": true,
+ "seconds": 0.3237298340536654,
+ "details": {
+ "patch_sha256": "9e53e5ac4daa21d8ee93a51c94a60aec14fbee520d48ef683517fc250a456800",
+ "actual_git_apply": true,
+ "installed_tests": 3,
+ "original_inputs_preserved": true
+ }
+ },
+ {
+ "name": "refusals_before_execution_or_generation",
+ "pass": true,
+ "seconds": 1.407257082988508,
+ "details": {
+ "cases": [
+ {
+ "target": "settings.py::missing",
+ "exit": 2,
+ "model_calls": 0,
+ "output_absent": true
+ },
+ {
+ "target": "ambiguous.py::same",
+ "exit": 2,
+ "model_calls": 0,
+ "output_absent": true
+ },
+ {
+ "target": "tests/test_existing.py::test_existing_stays_in_run",
+ "exit": 2,
+ "model_calls": 0,
+ "output_absent": true
+ },
+ {
+ "target": "../outside.py::hidden",
+ "exit": 2,
+ "model_calls": 0,
+ "output_absent": true
+ },
+ {
+ "target": "/Users/me/testpilot-explicit-target-review-a219f250962c/run-01/fixture-eg35j0kr/outside.py::hidden",
+ "exit": 2,
+ "model_calls": 0,
+ "output_absent": true
+ },
+ {
+ "target": "outside_link.py::hidden",
+ "exit": 2,
+ "model_calls": 0,
+ "output_absent": true
+ },
+ {
+ "target": "plain.txt::hidden",
+ "exit": 2,
+ "model_calls": 0,
+ "output_absent": true
+ },
+ {
+ "target": "badsyntax.py::broken",
+ "exit": 2,
+ "model_calls": 0,
+ "output_absent": true
+ },
+ {
+ "target": "settings.py",
+ "exit": 2,
+ "model_calls": 0,
+ "output_absent": true
+ },
+ {
+ "target": "settings.py::",
+ "exit": 2,
+ "model_calls": 0,
+ "output_absent": true
+ }
+ ],
+ "source_and_outside_bytes_unchanged": true
+ }
+ },
+ {
+ "name": "encoded_source_and_read_only_stdin_preview",
+ "pass": true,
+ "seconds": 0.15451562509406358,
+ "details": {
+ "unicode_name_and_encoding_cookie": true,
+ "stdin_context_exact": true,
+ "selected_module_not_imported": true
+ }
+ }
+ ],
+ "qualification": "Native synthetic fixtures and actual ScriptedModel only; no live model quality claim",
+ "preview": {
+ "changed_functions": [
+ {
+ "path": "settings.py",
+ "module": "settings",
+ "qualname": "Policy.cap",
+ "lineno": 13,
+ "end_lineno": 15,
+ "changed_lines": [],
+ "source": " @staticmethod\n def cap(amount, limit=LIMIT):\n return min(amount, limit)",
+ "is_method": true
+ },
+ {
+ "path": "settings.py",
+ "module": "settings",
+ "qualname": "accepts",
+ "lineno": 4,
+ "end_lineno": 5,
+ "changed_lines": [],
+ "source": "def accepts(amount):\n return amount <= LIMIT",
+ "is_method": false
+ }
+ ],
+ "selection": {
+ "mode": "explicit",
+ "requested": [
+ "settings.py::Policy.cap",
+ "settings.py::accepts",
+ "settings.py::Policy.cap"
+ ],
+ "reason": "Caller selected these targets; no dependency inference was performed.",
+ "diff_text": "diff --git a/settings.py b/settings.py\nindex 6a8ed7c..572bbe2 100644\n--- a/settings.py\n+++ b/settings.py\n@@ -1,4 +1,4 @@\n-LIMIT = 10\n+LIMIT = 7\n \n \n def accepts(amount):\n@@ -6,7 +6,7 @@ def accepts(amount):\n \n \n def ignored(amount):\n- return amount + 5\n+ return amount + 6\n \n \n class Policy:\n"
+ }
+ },
+ "native_result": {
+ "status": "passed",
+ "changed_functions": [
+ {
+ "path": "settings.py",
+ "module": "settings",
+ "qualname": "Policy.cap",
+ "lineno": 13,
+ "end_lineno": 15,
+ "changed_lines": [],
+ "source": " @staticmethod\n def cap(amount, limit=LIMIT):\n return min(amount, limit)",
+ "is_method": true
+ },
+ {
+ "path": "settings.py",
+ "module": "settings",
+ "qualname": "accepts",
+ "lineno": 4,
+ "end_lineno": 5,
+ "changed_lines": [],
+ "source": "def accepts(amount):\n return amount <= LIMIT",
+ "is_method": false
+ }
+ ],
+ "repair_rounds_used": 1,
+ "max_repair_rounds": 1,
+ "test_files": {
+ "tests/test_selected.py": "from settings import accepts, Policy\n\ndef test_selected_limit():\n assert accepts(7) is True\n assert accepts(8) is False\n\ndef test_selected_default():\n assert Policy.cap(20) == 7\n"
+ },
+ "tests_written": 2,
+ "final": {
+ "returncode": 0,
+ "timed_out": false,
+ "duration_s": 0.736,
+ "timeout_s": 15.0,
+ "output": "... [100%]\n3 passed in 0.29s\n",
+ "cases": [
+ {
+ "nodeid": "tests.test_existing::test_existing_stays_in_run",
+ "outcome": "passed",
+ "message": "",
+ "generated_file": null
+ },
+ {
+ "nodeid": "test_selected_limit",
+ "outcome": "passed",
+ "message": "",
+ "generated_file": "tests/test_selected.py"
+ },
+ {
+ "nodeid": "test_selected_default",
+ "outcome": "passed",
+ "message": "",
+ "generated_file": "tests/test_selected.py"
+ }
+ ],
+ "coverage": {
+ "percent_total": 100.0,
+ "files": {
+ "settings.py": {
+ "executed": [
+ 1,
+ 4,
+ 5,
+ 8,
+ 9,
+ 12,
+ 13,
+ 14,
+ 15
+ ],
+ "missing": []
+ }
+ }
+ },
+ "generated_files": [
+ "tests/test_selected.py"
+ ],
+ "junit_available": true,
+ "passed": 3,
+ "failed": 0,
+ "errors": 0,
+ "skipped": 0,
+ "ok": true,
+ "summary": "3 passed, 0 failed, 0 errors, 0 skipped (rc=0); generated: 2 passed of 2 collected",
+ "generated": {
+ "collected": 2,
+ "passed": 2,
+ "failed": 0,
+ "error": 0,
+ "skipped": 0
+ }
+ },
+ "patch": "diff --git a/tests/test_selected.py b/tests/test_selected.py\nnew file mode 100644\n--- /dev/null\n+++ b/tests/test_selected.py\n@@ -0,0 +1,8 @@\n+from settings import accepts, Policy\n+\n+def test_selected_limit():\n+ assert accepts(7) is True\n+ assert accepts(8) is False\n+\n+def test_selected_default():\n+ assert Policy.cap(20) == 7\n",
+ "coverage": {
+ "total_before": 77.8,
+ "total_after": 100.0,
+ "total_delta": 22.2,
+ "changed_lines_executable": 0,
+ "changed_lines_before": null,
+ "changed_lines_after": null
+ },
+ "ledger": {
+ "total_tokens": 1199,
+ "total_cost_usd": 0.0,
+ "cost_is_complete": false,
+ "tokens_estimated": true,
+ "by_model": {
+ "nvidia/nemotron-3-super-120b-a12b": {
+ "calls": 1,
+ "prompt_tokens": 265,
+ "completion_tokens": 15,
+ "cost_usd": 0.0
+ },
+ "nvidia/Nemotron-3_5-Lightning": {
+ "calls": 2,
+ "prompt_tokens": 811,
+ "completion_tokens": 108,
+ "cost_usd": 0.0
+ }
+ },
+ "entries": [
+ {
+ "role": "planner",
+ "model": "nvidia/nemotron-3-super-120b-a12b",
+ "prompt_tokens": 265,
+ "completion_tokens": 15,
+ "estimated": true,
+ "cost_usd": 0.0
+ },
+ {
+ "role": "editor",
+ "model": "nvidia/Nemotron-3_5-Lightning",
+ "prompt_tokens": 332,
+ "completion_tokens": 50,
+ "estimated": true,
+ "cost_usd": 0.0
+ },
+ {
+ "role": "repair",
+ "model": "nvidia/Nemotron-3_5-Lightning",
+ "prompt_tokens": 479,
+ "completion_tokens": 58,
+ "estimated": true,
+ "cost_usd": 0.0
+ }
+ ]
+ },
+ "rounds": [
+ {
+ "round": 0,
+ "kind": "generate",
+ "files": [
+ "tests/test_selected.py"
+ ],
+ "result": {
+ "returncode": 1,
+ "timed_out": false,
+ "duration_s": 0.619,
+ "timeout_s": 15.0,
+ "output": ".F. [100%]\n=================================== FAILURES ===================================\n_____________________________ test_selected_limit ______________________________\n\n def test_selected_limit():\n> assert accepts(8) is True\nE assert False is True\nE + where False = accepts(8)\n\ntests/test_selected.py:4: AssertionError\n=========================== short test summary info ============================\nFAILED ../../../../tmp/testpilot-_07ufgwy/repo::test_selected_limit - assert ...\n1 failed, 2 passed in 0.29s\n",
+ "cases": [
+ {
+ "nodeid": "tests.test_existing::test_existing_stays_in_run",
+ "outcome": "passed",
+ "message": "",
+ "generated_file": null
+ },
+ {
+ "nodeid": "test_selected_limit",
+ "outcome": "failed",
+ "message": "assert False is True\n + where False = accepts(8)\ndef test_selected_limit():\n> assert accepts(8) is True\nE assert False is True\nE + where False = accepts(8)\n\ntests/test_selected.py:4: AssertionError",
+ "generated_file": "tests/test_selected.py"
+ },
+ {
+ "nodeid": "test_selected_default",
+ "outcome": "passed",
+ "message": "",
+ "generated_file": "tests/test_selected.py"
+ }
+ ],
+ "coverage": {
+ "percent_total": 100.0,
+ "files": {
+ "settings.py": {
+ "executed": [
+ 1,
+ 4,
+ 5,
+ 8,
+ 9,
+ 12,
+ 13,
+ 14,
+ 15
+ ],
+ "missing": []
+ }
+ }
+ },
+ "generated_files": [
+ "tests/test_selected.py"
+ ],
+ "junit_available": true,
+ "passed": 2,
+ "failed": 1,
+ "errors": 0,
+ "skipped": 0,
+ "ok": false,
+ "summary": "2 passed, 1 failed, 0 errors, 0 skipped (rc=1); generated: 1 passed of 2 collected",
+ "generated": {
+ "collected": 2,
+ "passed": 1,
+ "failed": 1,
+ "error": 0,
+ "skipped": 0
+ }
+ },
+ "warnings": [],
+ "contents": {
+ "tests/test_selected.py": "from settings import accepts, Policy\n\ndef test_selected_limit():\n assert accepts(8) is True\n\ndef test_selected_default():\n assert Policy.cap(20) == 7\n"
+ }
+ },
+ {
+ "round": 1,
+ "kind": "repair",
+ "files": [
+ "tests/test_selected.py"
+ ],
+ "result": {
+ "returncode": 0,
+ "timed_out": false,
+ "duration_s": 0.736,
+ "timeout_s": 15.0,
+ "output": "... [100%]\n3 passed in 0.29s\n",
+ "cases": [
+ {
+ "nodeid": "tests.test_existing::test_existing_stays_in_run",
+ "outcome": "passed",
+ "message": "",
+ "generated_file": null
+ },
+ {
+ "nodeid": "test_selected_limit",
+ "outcome": "passed",
+ "message": "",
+ "generated_file": "tests/test_selected.py"
+ },
+ {
+ "nodeid": "test_selected_default",
+ "outcome": "passed",
+ "message": "",
+ "generated_file": "tests/test_selected.py"
+ }
+ ],
+ "coverage": {
+ "percent_total": 100.0,
+ "files": {
+ "settings.py": {
+ "executed": [
+ 1,
+ 4,
+ 5,
+ 8,
+ 9,
+ 12,
+ 13,
+ 14,
+ 15
+ ],
+ "missing": []
+ }
+ }
+ },
+ "generated_files": [
+ "tests/test_selected.py"
+ ],
+ "junit_available": true,
+ "passed": 3,
+ "failed": 0,
+ "errors": 0,
+ "skipped": 0,
+ "ok": true,
+ "summary": "3 passed, 0 failed, 0 errors, 0 skipped (rc=0); generated: 2 passed of 2 collected",
+ "generated": {
+ "collected": 2,
+ "passed": 2,
+ "failed": 0,
+ "error": 0,
+ "skipped": 0
+ }
+ },
+ "warnings": [],
+ "contents": {
+ "tests/test_selected.py": "from settings import accepts, Policy\n\ndef test_selected_limit():\n assert accepts(7) is True\n assert accepts(8) is False\n\ndef test_selected_default():\n assert Policy.cap(20) == 7\n"
+ }
+ }
+ ],
+ "plan": "Pin only the caller-selected boundary and captured default.",
+ "message": "",
+ "selection": {
+ "mode": "explicit",
+ "requested": [
+ "settings.py::Policy.cap",
+ "settings.py::accepts",
+ "settings.py::Policy.cap"
+ ],
+ "reason": "Caller selected these targets; no dependency inference was performed.",
+ "diff_text": "diff --git a/settings.py b/settings.py\nindex 6a8ed7c..572bbe2 100644\n--- a/settings.py\n+++ b/settings.py\n@@ -1,4 +1,4 @@\n-LIMIT = 10\n+LIMIT = 7\n \n \n def accepts(amount):\n@@ -6,7 +6,7 @@ def accepts(amount):\n \n \n def ignored(amount):\n- return amount + 5\n+ return amount + 6\n \n \n class Policy:\n"
+ },
+ "ok": true
+ },
+ "package_source_unchanged": true,
+ "baseline_source_unchanged": true,
+ "source_fingerprints": {
+ "__init__.py": {
+ "bytes": 390,
+ "sha256": "65c63b80d7da34a83a6049a79c24b8335878cd49cc79951fdefc5f1532ae841f"
+ },
+ "__main__.py": {
+ "bytes": 7479,
+ "sha256": "319bc6e8b06615d657530eb35fa2f7f3e6a883a36c98ea1540fdf4c2815eac55"
+ },
+ "_pytest_generated.py": {
+ "bytes": 2943,
+ "sha256": "d65352d7bade965d7b9dc73efe5d4d01844fa8c7474a1bdcdb3b708f6decf768"
+ },
+ "diff.py": {
+ "bytes": 9396,
+ "sha256": "659021d4a4b462b86eadc25957407b683c733fec60ae33c0703295cb7640a06c"
+ },
+ "html_report.py": {
+ "bytes": 20982,
+ "sha256": "f2b65ee9201f9e91ad183fcb8f6b8bf6e69c77b1a066fcd90bb431951e41a2f6"
+ },
+ "loop.py": {
+ "bytes": 25406,
+ "sha256": "3ea786ae5466ebdd6087b03d829580c333882e6fd429aaf54099320b4aa0bb08"
+ },
+ "model.py": {
+ "bytes": 10175,
+ "sha256": "f6ae697f1f5d686ffdcb96f5451d1a9600d7d41c7bcf324d7e8c276a107e8673"
+ },
+ "recheck.py": {
+ "bytes": 11599,
+ "sha256": "1ecbe0ff7da7bb4a8117d730a76624c8929e0e4b10cb9d502ab7358cb38d8f2e"
+ },
+ "sandbox.py": {
+ "bytes": 12721,
+ "sha256": "8d5aadafff5188473ec45eb1cd8f01fb562836ff05bf12a27939cde83b33472a"
+ },
+ "targets.py": {
+ "bytes": 5410,
+ "sha256": "35add23585b0e88246288572f49bd857ebf4af83a221b9129e784e9b456c1ab0"
+ }
+ },
+ "finished_at": 1791472099.009901,
+ "accepted": true
+}
diff --git a/docs/receiving/explicit-targets-a219f250962c/independent/source-binding.json b/docs/receiving/explicit-targets-a219f250962c/independent/source-binding.json
new file mode 100644
index 0000000..3f2059d
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/independent/source-binding.json
@@ -0,0 +1,225 @@
+{
+ "source_commit": "00b63c8e03cc515160da1202331224167d12d253",
+ "source_tree": "d1ba76e3872a0e843821b799f8accb69aa32416d",
+ "canonical_base": "63174afd8e63a0ea562cf188b7d971522f017a30",
+ "scope": "27-leaf partial source snapshot",
+ "source_clean": true,
+ "files": [
+ {
+ "path": "README.md",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "3848e40614d7328eb5544a28f52cd6a75de38aa1",
+ "bytes": 12053,
+ "sha256": "9c646cbd14a07a5af79afd9a306202d402e2037db7e6557525d9e18387e27f5a"
+ },
+ {
+ "path": "docs/EXPLICIT_TARGETS.md",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "3cfe7a0370d48a1e86c6f7c49bb7508fbdfb8abd",
+ "bytes": 5497,
+ "sha256": "dabf34adc34d931749f04debe601fb8f2c6a0ab668278c8539926fb05c88cca8"
+ },
+ {
+ "path": "docs/RECHECK.md",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "b7eff4e6089c2974b8c277370f474935d4bf4806",
+ "bytes": 5885,
+ "sha256": "7855d34a1e84a4fb4b4ef113aa4bc488ccf28933883623e099ae427cc0fdbfc3"
+ },
+ {
+ "path": "pyproject.toml",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "952255ac87e2c7da57457fdbf3554914f17f6d05",
+ "bytes": 676,
+ "sha256": "5503eab7b6c145d4de241e5be805a4fb8f30828053cbe39dc05a43c2896642eb"
+ },
+ {
+ "path": "testpilot/__init__.py",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "296210dd0d58703de4a4e4c9ad72a0944c9400ca",
+ "bytes": 390,
+ "sha256": "65c63b80d7da34a83a6049a79c24b8335878cd49cc79951fdefc5f1532ae841f"
+ },
+ {
+ "path": "testpilot/__main__.py",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "5686cb41d555890b906d2ea322149f0673e8049d",
+ "bytes": 7479,
+ "sha256": "319bc6e8b06615d657530eb35fa2f7f3e6a883a36c98ea1540fdf4c2815eac55"
+ },
+ {
+ "path": "testpilot/_pytest_generated.py",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "90b82725bc0eb1ff3d7cb517c4c9dedcfd14bdcc",
+ "bytes": 2943,
+ "sha256": "d65352d7bade965d7b9dc73efe5d4d01844fa8c7474a1bdcdb3b708f6decf768"
+ },
+ {
+ "path": "testpilot/diff.py",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "f92280a9ad3f9f79713dce7ba7ae28c55414783a",
+ "bytes": 9396,
+ "sha256": "659021d4a4b462b86eadc25957407b683c733fec60ae33c0703295cb7640a06c"
+ },
+ {
+ "path": "testpilot/html_report.py",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "2fcc6441473684c2f8bdd16d314e8b258de51edf",
+ "bytes": 20982,
+ "sha256": "f2b65ee9201f9e91ad183fcb8f6b8bf6e69c77b1a066fcd90bb431951e41a2f6"
+ },
+ {
+ "path": "testpilot/loop.py",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "8a567fb35f29dc242413df3678f5f63fe08caa4f",
+ "bytes": 25406,
+ "sha256": "3ea786ae5466ebdd6087b03d829580c333882e6fd429aaf54099320b4aa0bb08"
+ },
+ {
+ "path": "testpilot/model.py",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "6d4abca38cc1c256df00bcac7923e2dc0bd42b16",
+ "bytes": 10175,
+ "sha256": "f6ae697f1f5d686ffdcb96f5451d1a9600d7d41c7bcf324d7e8c276a107e8673"
+ },
+ {
+ "path": "testpilot/recheck.py",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "4ab26cbb83beff563b9c3683c593b3efd2495b6b",
+ "bytes": 11599,
+ "sha256": "1ecbe0ff7da7bb4a8117d730a76624c8929e0e4b10cb9d502ab7358cb38d8f2e"
+ },
+ {
+ "path": "testpilot/sandbox.py",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "8ca4a4102c7890e89eae99548e28ba8325974f1e",
+ "bytes": 12721,
+ "sha256": "8d5aadafff5188473ec45eb1cd8f01fb562836ff05bf12a27939cde83b33472a"
+ },
+ {
+ "path": "testpilot/targets.py",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "2da1600eca93edbe47605e6ca2a9e715663fa370",
+ "bytes": 5410,
+ "sha256": "35add23585b0e88246288572f49bd857ebf4af83a221b9129e784e9b456c1ab0"
+ },
+ {
+ "path": "tests/conftest.py",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "2a855d9fdb8debae0b082088580a4c41e499e33c",
+ "bytes": 144,
+ "sha256": "0cb67ba91674c07e557440292720aa3c3c111f4c41b7db1f4d219a7c68dc6f52"
+ },
+ {
+ "path": "tests/test_cli_diff_transport.py",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "25017791488cf8dd3c7f95b9460d73d691ce8a06",
+ "bytes": 11507,
+ "sha256": "5748cd3aafbd5aa032b98d531a9091d4a91becdce187a514dafffcc271173bab"
+ },
+ {
+ "path": "tests/test_cli_targets.py",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "281550909a8cce1067983199c5122e1bc5c0b78e",
+ "bytes": 10853,
+ "sha256": "f53a8a7ed6ac752e5bfc1e32a92c88d5985e1da2a8296f317df2ea5b11f5c082"
+ },
+ {
+ "path": "tests/test_diff_source_boundary.py",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "74f9ca4d4a31b9ca447f362ac3cbf594333d560f",
+ "bytes": 5521,
+ "sha256": "f8442bb2e26a4cbdfa7b6320627c780a83264675959ddc6339bc88b57a6faec2"
+ },
+ {
+ "path": "tests/test_explicit_target_cli.py",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "d01e0dad0cb73fe1f54b6c88d8287792dd587869",
+ "bytes": 1930,
+ "sha256": "01eb0a6782448a59bbf876fa1d8cbf8398bb05b461f5875b4d125b0147ab3365"
+ },
+ {
+ "path": "tests/test_explicit_targets.py",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "438d1c23335d79d4151a198f7e3606595cd68785",
+ "bytes": 5268,
+ "sha256": "bd40f190a52195eed73145cbd0a721b0c555307dfd358ec1d795df3799026b1d"
+ },
+ {
+ "path": "tests/test_fenced_code_boundaries.py",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "54c798f49b690d6365ec4de9b0ae78f9d99cd960",
+ "bytes": 2990,
+ "sha256": "edff7f6dec6c754cdbd03efee84b342103bb1c43a8b614333e1ca83e75bfd91c"
+ },
+ {
+ "path": "tests/test_html_report.py",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "655c4ce97b5ffbd2fe81f5b602a2ca12388c27b7",
+ "bytes": 9811,
+ "sha256": "bb2d6b66ecf929ee6340ae6f39875a07e64dd8c5af2053944b16ad93aa7d6f5c"
+ },
+ {
+ "path": "tests/test_loop.py",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "8540b31caf7c258e9345e62d11cd4796c5e92ba5",
+ "bytes": 5465,
+ "sha256": "efcf833870d9fb407d324c48d8421d587089407b4b1db9f7ba24fad7c2ed67dc"
+ },
+ {
+ "path": "tests/test_patch_physical_lines.py",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "f5a9ff15106d39c953cda3f783f64362634f7ad1",
+ "bytes": 3085,
+ "sha256": "8318fb4e20f87e4f0cc25013aab9f18b3a4236537bbae5f2fbb7e56a1b2a825b"
+ },
+ {
+ "path": "tests/test_python_source_encoding.py",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "903abd6db569a80e3a5a61d734bf376c0ac4c8dd",
+ "bytes": 5598,
+ "sha256": "85196e144916098af0fe677f01382eeb48c6f175008c76446756c438ee741b89"
+ },
+ {
+ "path": "tests/test_recheck.py",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "6ec7e36cd054d320842b5cdcfd34b635d4dc64f4",
+ "bytes": 13652,
+ "sha256": "dc74c36ddc2f2bd62505549648d0572e5a388840076c8965338f23d371173760"
+ },
+ {
+ "path": "tests/test_report_text_encoding.py",
+ "mode": "100644",
+ "type": "blob",
+ "git_blob_sha": "44e7622672361ffad936e462b0996816c55d2ddd",
+ "bytes": 9330,
+ "sha256": "1dde3e4f3b227918e541e4ffc71099a8f36c235c198169bb1a152302ea635a3e"
+ }
+ ]
+}
diff --git a/docs/receiving/explicit-targets-a219f250962c/independent/source-freeze.json b/docs/receiving/explicit-targets-a219f250962c/independent/source-freeze.json
new file mode 100644
index 0000000..074d7aa
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/independent/source-freeze.json
@@ -0,0 +1,203 @@
+{
+ "repository": "Jacob-Met/testpilot",
+ "source_path": "/Users/me/hamon-testpilot-explicit-targets-a219f250962c",
+ "code_commit": "00b63c8e03cc515160da1202331224167d12d253",
+ "tree": "d1ba76e3872a0e843821b799f8accb69aa32416d",
+ "canonical_base": "63174afd8e63a0ea562cf188b7d971522f017a30",
+ "canonical_base_tree": "cea947d9aeb2253466593044889a1bb548f8b7c4",
+ "scoped_base": "33a10cd5b756b8ade8768bd6f8da75f55da369c3",
+ "scoped_base_tree": "a011210502f9171c1b6cffff76342b13fb755b85",
+ "authored_previous_commit": "fc24a55d2c5e2d94478072e8e6736bf44bab81a6",
+ "authored_previous_tree": "6b2582c2041bfa9a7cb3600bcead12574f6230c9",
+ "author_paths": [
+ "README.md",
+ "docs/EXPLICIT_TARGETS.md",
+ "testpilot/__main__.py",
+ "testpilot/html_report.py",
+ "testpilot/loop.py",
+ "testpilot/targets.py",
+ "tests/test_explicit_target_cli.py",
+ "tests/test_explicit_targets.py"
+ ],
+ "identical_author_paths_from_previous": [
+ "testpilot/targets.py",
+ "testpilot/loop.py",
+ "testpilot/html_report.py",
+ "README.md",
+ "docs/EXPLICIT_TARGETS.md",
+ "tests/test_explicit_targets.py",
+ "tests/test_explicit_target_cli.py"
+ ],
+ "unchanged_current_parent_files": 19,
+ "files": [
+ {
+ "path": "README.md",
+ "bytes": 12053,
+ "sha256": "9c646cbd14a07a5af79afd9a306202d402e2037db7e6557525d9e18387e27f5a",
+ "git_blob": "3848e40614d7328eb5544a28f52cd6a75de38aa1"
+ },
+ {
+ "path": "docs/EXPLICIT_TARGETS.md",
+ "bytes": 5497,
+ "sha256": "dabf34adc34d931749f04debe601fb8f2c6a0ab668278c8539926fb05c88cca8",
+ "git_blob": "3cfe7a0370d48a1e86c6f7c49bb7508fbdfb8abd"
+ },
+ {
+ "path": "docs/RECHECK.md",
+ "bytes": 5885,
+ "sha256": "7855d34a1e84a4fb4b4ef113aa4bc488ccf28933883623e099ae427cc0fdbfc3",
+ "git_blob": "b7eff4e6089c2974b8c277370f474935d4bf4806"
+ },
+ {
+ "path": "pyproject.toml",
+ "bytes": 676,
+ "sha256": "5503eab7b6c145d4de241e5be805a4fb8f30828053cbe39dc05a43c2896642eb",
+ "git_blob": "952255ac87e2c7da57457fdbf3554914f17f6d05"
+ },
+ {
+ "path": "testpilot/__init__.py",
+ "bytes": 390,
+ "sha256": "65c63b80d7da34a83a6049a79c24b8335878cd49cc79951fdefc5f1532ae841f",
+ "git_blob": "296210dd0d58703de4a4e4c9ad72a0944c9400ca"
+ },
+ {
+ "path": "testpilot/__main__.py",
+ "bytes": 7479,
+ "sha256": "319bc6e8b06615d657530eb35fa2f7f3e6a883a36c98ea1540fdf4c2815eac55",
+ "git_blob": "5686cb41d555890b906d2ea322149f0673e8049d"
+ },
+ {
+ "path": "testpilot/_pytest_generated.py",
+ "bytes": 2943,
+ "sha256": "d65352d7bade965d7b9dc73efe5d4d01844fa8c7474a1bdcdb3b708f6decf768",
+ "git_blob": "90b82725bc0eb1ff3d7cb517c4c9dedcfd14bdcc"
+ },
+ {
+ "path": "testpilot/diff.py",
+ "bytes": 9396,
+ "sha256": "659021d4a4b462b86eadc25957407b683c733fec60ae33c0703295cb7640a06c",
+ "git_blob": "f92280a9ad3f9f79713dce7ba7ae28c55414783a"
+ },
+ {
+ "path": "testpilot/html_report.py",
+ "bytes": 20982,
+ "sha256": "f2b65ee9201f9e91ad183fcb8f6b8bf6e69c77b1a066fcd90bb431951e41a2f6",
+ "git_blob": "2fcc6441473684c2f8bdd16d314e8b258de51edf"
+ },
+ {
+ "path": "testpilot/loop.py",
+ "bytes": 25406,
+ "sha256": "3ea786ae5466ebdd6087b03d829580c333882e6fd429aaf54099320b4aa0bb08",
+ "git_blob": "8a567fb35f29dc242413df3678f5f63fe08caa4f"
+ },
+ {
+ "path": "testpilot/model.py",
+ "bytes": 10175,
+ "sha256": "f6ae697f1f5d686ffdcb96f5451d1a9600d7d41c7bcf324d7e8c276a107e8673",
+ "git_blob": "6d4abca38cc1c256df00bcac7923e2dc0bd42b16"
+ },
+ {
+ "path": "testpilot/recheck.py",
+ "bytes": 11599,
+ "sha256": "1ecbe0ff7da7bb4a8117d730a76624c8929e0e4b10cb9d502ab7358cb38d8f2e",
+ "git_blob": "4ab26cbb83beff563b9c3683c593b3efd2495b6b"
+ },
+ {
+ "path": "testpilot/sandbox.py",
+ "bytes": 12721,
+ "sha256": "8d5aadafff5188473ec45eb1cd8f01fb562836ff05bf12a27939cde83b33472a",
+ "git_blob": "8ca4a4102c7890e89eae99548e28ba8325974f1e"
+ },
+ {
+ "path": "testpilot/targets.py",
+ "bytes": 5410,
+ "sha256": "35add23585b0e88246288572f49bd857ebf4af83a221b9129e784e9b456c1ab0",
+ "git_blob": "2da1600eca93edbe47605e6ca2a9e715663fa370"
+ },
+ {
+ "path": "tests/conftest.py",
+ "bytes": 144,
+ "sha256": "0cb67ba91674c07e557440292720aa3c3c111f4c41b7db1f4d219a7c68dc6f52",
+ "git_blob": "2a855d9fdb8debae0b082088580a4c41e499e33c"
+ },
+ {
+ "path": "tests/test_cli_diff_transport.py",
+ "bytes": 11507,
+ "sha256": "5748cd3aafbd5aa032b98d531a9091d4a91becdce187a514dafffcc271173bab",
+ "git_blob": "25017791488cf8dd3c7f95b9460d73d691ce8a06"
+ },
+ {
+ "path": "tests/test_cli_targets.py",
+ "bytes": 10853,
+ "sha256": "f53a8a7ed6ac752e5bfc1e32a92c88d5985e1da2a8296f317df2ea5b11f5c082",
+ "git_blob": "281550909a8cce1067983199c5122e1bc5c0b78e"
+ },
+ {
+ "path": "tests/test_diff_source_boundary.py",
+ "bytes": 5521,
+ "sha256": "f8442bb2e26a4cbdfa7b6320627c780a83264675959ddc6339bc88b57a6faec2",
+ "git_blob": "74f9ca4d4a31b9ca447f362ac3cbf594333d560f"
+ },
+ {
+ "path": "tests/test_explicit_target_cli.py",
+ "bytes": 1930,
+ "sha256": "01eb0a6782448a59bbf876fa1d8cbf8398bb05b461f5875b4d125b0147ab3365",
+ "git_blob": "d01e0dad0cb73fe1f54b6c88d8287792dd587869"
+ },
+ {
+ "path": "tests/test_explicit_targets.py",
+ "bytes": 5268,
+ "sha256": "bd40f190a52195eed73145cbd0a721b0c555307dfd358ec1d795df3799026b1d",
+ "git_blob": "438d1c23335d79d4151a198f7e3606595cd68785"
+ },
+ {
+ "path": "tests/test_fenced_code_boundaries.py",
+ "bytes": 2990,
+ "sha256": "edff7f6dec6c754cdbd03efee84b342103bb1c43a8b614333e1ca83e75bfd91c",
+ "git_blob": "54c798f49b690d6365ec4de9b0ae78f9d99cd960"
+ },
+ {
+ "path": "tests/test_html_report.py",
+ "bytes": 9811,
+ "sha256": "bb2d6b66ecf929ee6340ae6f39875a07e64dd8c5af2053944b16ad93aa7d6f5c",
+ "git_blob": "655c4ce97b5ffbd2fe81f5b602a2ca12388c27b7"
+ },
+ {
+ "path": "tests/test_loop.py",
+ "bytes": 5465,
+ "sha256": "efcf833870d9fb407d324c48d8421d587089407b4b1db9f7ba24fad7c2ed67dc",
+ "git_blob": "8540b31caf7c258e9345e62d11cd4796c5e92ba5"
+ },
+ {
+ "path": "tests/test_patch_physical_lines.py",
+ "bytes": 3085,
+ "sha256": "8318fb4e20f87e4f0cc25013aab9f18b3a4236537bbae5f2fbb7e56a1b2a825b",
+ "git_blob": "f5a9ff15106d39c953cda3f783f64362634f7ad1"
+ },
+ {
+ "path": "tests/test_python_source_encoding.py",
+ "bytes": 5598,
+ "sha256": "85196e144916098af0fe677f01382eeb48c6f175008c76446756c438ee741b89",
+ "git_blob": "903abd6db569a80e3a5a61d734bf376c0ac4c8dd"
+ },
+ {
+ "path": "tests/test_recheck.py",
+ "bytes": 13652,
+ "sha256": "dc74c36ddc2f2bd62505549648d0572e5a388840076c8965338f23d371173760",
+ "git_blob": "6ec7e36cd054d320842b5cdcfd34b635d4dc64f4"
+ },
+ {
+ "path": "tests/test_report_text_encoding.py",
+ "bytes": 9330,
+ "sha256": "1dde3e4f3b227918e541e4ffc71099a8f36c235c198169bb1a152302ea635a3e",
+ "git_blob": "44e7622672361ffad936e462b0996816c55d2ddd"
+ }
+ ],
+ "receiving_scope": "Source/CLI/prompt/sandbox/patch receiving pending independent root disposition; baseline-matched native existing-test limitations retained",
+ "native_results": {
+ "old_e228": "126 passed,18 subtests passed,2 existing filename fixture failures reproduced on untouched baseline",
+ "current_631": "58 passed,18 subtests passed,1 existing skip,1 existing configured-suite expected-count failure reproduced on untouched baseline",
+ "new_tests": "All12 new explicit-target tests pass in both source compositions"
+ },
+ "clean": true
+}
diff --git a/docs/receiving/explicit-targets-a219f250962c/source/authored-source-pin.json b/docs/receiving/explicit-targets-a219f250962c/source/authored-source-pin.json
new file mode 100644
index 0000000..27ff09a
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/source/authored-source-pin.json
@@ -0,0 +1,59 @@
+{
+ "code_commit": "fc24a55d2c5e2d94478072e8e6736bf44bab81a6",
+ "tree": "6b2582c2041bfa9a7cb3600bcead12574f6230c9",
+ "canonical_base": "e2285d68b2ea5eb2158c0a7e0d936ce5d56f8ff5",
+ "scoped_base": "a67abc30301dbcb357e866de749502045e5e40fd",
+ "files": [
+ {
+ "path": "testpilot/targets.py",
+ "sha256": "35add23585b0e88246288572f49bd857ebf4af83a221b9129e784e9b456c1ab0"
+ },
+ {
+ "path": "testpilot/__main__.py",
+ "sha256": "95f80ec643e30659b6fb1623c303d17246a36e3f3c5cc59cc8723fd71cce3ee1"
+ },
+ {
+ "path": "testpilot/loop.py",
+ "sha256": "3ea786ae5466ebdd6087b03d829580c333882e6fd429aaf54099320b4aa0bb08"
+ },
+ {
+ "path": "testpilot/html_report.py",
+ "sha256": "f2b65ee9201f9e91ad183fcb8f6b8bf6e69c77b1a066fcd90bb431951e41a2f6"
+ },
+ {
+ "path": "README.md",
+ "sha256": "9c646cbd14a07a5af79afd9a306202d402e2037db7e6557525d9e18387e27f5a"
+ },
+ {
+ "path": "docs/EXPLICIT_TARGETS.md",
+ "sha256": "dabf34adc34d931749f04debe601fb8f2c6a0ab668278c8539926fb05c88cca8"
+ },
+ {
+ "path": "tests/test_explicit_targets.py",
+ "sha256": "bd40f190a52195eed73145cbd0a721b0c555307dfd358ec1d795df3799026b1d"
+ },
+ {
+ "path": "tests/test_explicit_target_cli.py",
+ "sha256": "01eb0a6782448a59bbf876fa1d8cbf8398bb05b461f5875b4d125b0147ab3365"
+ }
+ ],
+ "preserved_loop_functions": [
+ "sanitize_test_path",
+ "parse_test_files",
+ "parse_verdict",
+ "_path_occupied",
+ "_require_additions",
+ "_pytest_module_name",
+ "_repository_module_names",
+ "_merge_test_files",
+ "make_patch",
+ "_changed_line_coverage",
+ "coverage_delta",
+ "_functions_block",
+ "_files_block",
+ "count_tests",
+ "write_outputs"
+ ],
+ "read_diff_exact": true,
+ "clean": true
+}
diff --git a/docs/receiving/explicit-targets-a219f250962c/source/current-baseline-manifest.json b/docs/receiving/explicit-targets-a219f250962c/source/current-baseline-manifest.json
new file mode 100644
index 0000000..4fb9801
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/source/current-baseline-manifest.json
@@ -0,0 +1,146 @@
+{
+ "canonical_commit": "63174afd8e63a0ea562cf188b7d971522f017a30",
+ "canonical_tree": "cea947d9aeb2253466593044889a1bb548f8b7c4",
+ "scoped_commit": "33a10cd5b756b8ade8768bd6f8da75f55da369c3",
+ "scoped_tree": "a011210502f9171c1b6cffff76342b13fb755b85",
+ "files": [
+ {
+ "path": "testpilot/__main__.py",
+ "git_blob": "2d98e2a85320ac2827ea48fa9d58d30a60354b6a",
+ "size": 6065,
+ "sha256": "90577ea3a939642f5cb29818f6360df96d9fa0d35d05af11bf97054425c32c84"
+ },
+ {
+ "path": "testpilot/diff.py",
+ "git_blob": "f92280a9ad3f9f79713dce7ba7ae28c55414783a",
+ "size": 9396,
+ "sha256": "659021d4a4b462b86eadc25957407b683c733fec60ae33c0703295cb7640a06c"
+ },
+ {
+ "path": "testpilot/loop.py",
+ "git_blob": "3057a9c7c9777a23e73f8b56979670001eb512fd",
+ "size": 24064,
+ "sha256": "990d6df8b65877e6f25e319771ccad189767575587b4a165f3c3a8e2280c4c93"
+ },
+ {
+ "path": "testpilot/__init__.py",
+ "git_blob": "296210dd0d58703de4a4e4c9ad72a0944c9400ca",
+ "size": 390,
+ "sha256": "65c63b80d7da34a83a6049a79c24b8335878cd49cc79951fdefc5f1532ae841f"
+ },
+ {
+ "path": "testpilot/model.py",
+ "git_blob": "6d4abca38cc1c256df00bcac7923e2dc0bd42b16",
+ "size": 10175,
+ "sha256": "f6ae697f1f5d686ffdcb96f5451d1a9600d7d41c7bcf324d7e8c276a107e8673"
+ },
+ {
+ "path": "testpilot/sandbox.py",
+ "git_blob": "8ca4a4102c7890e89eae99548e28ba8325974f1e",
+ "size": 12721,
+ "sha256": "8d5aadafff5188473ec45eb1cd8f01fb562836ff05bf12a27939cde83b33472a"
+ },
+ {
+ "path": "testpilot/html_report.py",
+ "git_blob": "bb30d903542d836f5afe8e116ba894f93d9d3a0c",
+ "size": 20656,
+ "sha256": "183703d05f32552f7f7c39aa958c39bae61d824e3fa9cd6a6dd686f0d4c228a4"
+ },
+ {
+ "path": "testpilot/_pytest_generated.py",
+ "git_blob": "90b82725bc0eb1ff3d7cb517c4c9dedcfd14bdcc",
+ "size": 2943,
+ "sha256": "d65352d7bade965d7b9dc73efe5d4d01844fa8c7474a1bdcdb3b708f6decf768"
+ },
+ {
+ "path": "tests/conftest.py",
+ "git_blob": "2a855d9fdb8debae0b082088580a4c41e499e33c",
+ "size": 144,
+ "sha256": "0cb67ba91674c07e557440292720aa3c3c111f4c41b7db1f4d219a7c68dc6f52"
+ },
+ {
+ "path": "tests/test_cli_diff_transport.py",
+ "git_blob": "25017791488cf8dd3c7f95b9460d73d691ce8a06",
+ "size": 11507,
+ "sha256": "5748cd3aafbd5aa032b98d531a9091d4a91becdce187a514dafffcc271173bab"
+ },
+ {
+ "path": "tests/test_cli_targets.py",
+ "git_blob": "281550909a8cce1067983199c5122e1bc5c0b78e",
+ "size": 10853,
+ "sha256": "f53a8a7ed6ac752e5bfc1e32a92c88d5985e1da2a8296f317df2ea5b11f5c082"
+ },
+ {
+ "path": "tests/test_diff_source_boundary.py",
+ "git_blob": "74f9ca4d4a31b9ca447f362ac3cbf594333d560f",
+ "size": 5521,
+ "sha256": "f8442bb2e26a4cbdfa7b6320627c780a83264675959ddc6339bc88b57a6faec2"
+ },
+ {
+ "path": "tests/test_fenced_code_boundaries.py",
+ "git_blob": "54c798f49b690d6365ec4de9b0ae78f9d99cd960",
+ "size": 2990,
+ "sha256": "edff7f6dec6c754cdbd03efee84b342103bb1c43a8b614333e1ca83e75bfd91c"
+ },
+ {
+ "path": "tests/test_html_report.py",
+ "git_blob": "655c4ce97b5ffbd2fe81f5b602a2ca12388c27b7",
+ "size": 9811,
+ "sha256": "bb2d6b66ecf929ee6340ae6f39875a07e64dd8c5af2053944b16ad93aa7d6f5c"
+ },
+ {
+ "path": "tests/test_loop.py",
+ "git_blob": "8540b31caf7c258e9345e62d11cd4796c5e92ba5",
+ "size": 5465,
+ "sha256": "efcf833870d9fb407d324c48d8421d587089407b4b1db9f7ba24fad7c2ed67dc"
+ },
+ {
+ "path": "tests/test_patch_physical_lines.py",
+ "git_blob": "f5a9ff15106d39c953cda3f783f64362634f7ad1",
+ "size": 3085,
+ "sha256": "8318fb4e20f87e4f0cc25013aab9f18b3a4236537bbae5f2fbb7e56a1b2a825b"
+ },
+ {
+ "path": "tests/test_python_source_encoding.py",
+ "git_blob": "903abd6db569a80e3a5a61d734bf376c0ac4c8dd",
+ "size": 5598,
+ "sha256": "85196e144916098af0fe677f01382eeb48c6f175008c76446756c438ee741b89"
+ },
+ {
+ "path": "tests/test_report_text_encoding.py",
+ "git_blob": "44e7622672361ffad936e462b0996816c55d2ddd",
+ "size": 9330,
+ "sha256": "1dde3e4f3b227918e541e4ffc71099a8f36c235c198169bb1a152302ea635a3e"
+ },
+ {
+ "path": "README.md",
+ "git_blob": "eaf87ecdc04aee56e7de8e5ec28ebaec20d0de14",
+ "size": 11550,
+ "sha256": "a75d56db555f787886056157c9d07d625ab18c5f06299561ae9889f3610efe00"
+ },
+ {
+ "path": "pyproject.toml",
+ "git_blob": "952255ac87e2c7da57457fdbf3554914f17f6d05",
+ "size": 676,
+ "sha256": "5503eab7b6c145d4de241e5be805a4fb8f30828053cbe39dc05a43c2896642eb"
+ },
+ {
+ "path": "docs/RECHECK.md",
+ "git_blob": "b7eff4e6089c2974b8c277370f474935d4bf4806",
+ "size": 5885,
+ "sha256": "7855d34a1e84a4fb4b4ef113aa4bc488ccf28933883623e099ae427cc0fdbfc3"
+ },
+ {
+ "path": "testpilot/recheck.py",
+ "git_blob": "4ab26cbb83beff563b9c3683c593b3efd2495b6b",
+ "size": 11599,
+ "sha256": "1ecbe0ff7da7bb4a8117d730a76624c8929e0e4b10cb9d502ab7358cb38d8f2e"
+ },
+ {
+ "path": "tests/test_recheck.py",
+ "git_blob": "6ec7e36cd054d320842b5cdcfd34b635d4dc64f4",
+ "size": 13652,
+ "sha256": "dc74c36ddc2f2bd62505549648d0572e5a388840076c8965338f23d371173760"
+ }
+ ]
+}
diff --git a/docs/receiving/explicit-targets-a219f250962c/source/explicit-targets.patch b/docs/receiving/explicit-targets-a219f250962c/source/explicit-targets.patch
new file mode 100644
index 0000000..973ec1a
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/source/explicit-targets.patch
@@ -0,0 +1,650 @@
+From 00b63c8e03cc515160da1202331224167d12d253 Mon Sep 17 00:00:00 2001
+From: estate-a219f250962c
+Date: Thu, 8 Oct 2026 08:06:12 -0700
+Subject: [PATCH] Compose explicit targets with canonical saved-test recheck
+
+---
+ README.md | 9 +++
+ docs/EXPLICIT_TARGETS.md | 111 +++++++++++++++++++++++++++
+ testpilot/__main__.py | 31 +++++++-
+ testpilot/html_report.py | 4 +
+ testpilot/loop.py | 42 ++++++++---
+ testpilot/targets.py | 120 ++++++++++++++++++++++++++++++
+ tests/test_explicit_target_cli.py | 41 ++++++++++
+ tests/test_explicit_targets.py | 102 +++++++++++++++++++++++++
+ 8 files changed, 445 insertions(+), 15 deletions(-)
+ create mode 100644 docs/EXPLICIT_TARGETS.md
+ create mode 100644 testpilot/targets.py
+ create mode 100644 tests/test_explicit_target_cli.py
+ create mode 100644 tests/test_explicit_targets.py
+
+diff --git a/README.md b/README.md
+index eaf87ec..3848e40 100644
+--- a/README.md
++++ b/README.md
+@@ -82,6 +82,15 @@ python -m testpilot run \
+
+ On a real repo, use `--git-base main` instead of `--diff FILE`. Exit codes: 0 means the generated tests pass, 1 means any other outcome, 2 means a config error.
+
++## Choose functions explicitly
++
++Use repeated `--target path.py::qualname` flags on `targets` or `run` to select
++specific current functions, including unchanged functions affected by a change
++you have identified. The supplied diff stays available as context to planning,
++generation and repair. Without this option, automatic diff selection is unchanged.
++See [explicit targets](docs/EXPLICIT_TARGETS.md) for preview, ordering, refusals,
++saved selection context and unchanged-function coverage semantics.
++
+ ## Review your own run in a browser
+
+ Every run also writes **`report.html`** in its output directory. Open that file
+diff --git a/docs/EXPLICIT_TARGETS.md b/docs/EXPLICIT_TARGETS.md
+new file mode 100644
+index 0000000..3cfe7a0
+--- /dev/null
++++ b/docs/EXPLICIT_TARGETS.md
+@@ -0,0 +1,111 @@
++# Explicit function targets
++
++Use this workflow when you know which function needs tests, including a function
++whose body did not change. For example, changing a module-level limit can change
++a function's behavior while the normal diff selector finds no changed function.
++
++Preview the current source before generating:
++
++~~~bash
++python -m testpilot targets --repo . --diff change.diff \
++ --target settings.py::accepts --json
++
++python -m testpilot run --repo . --diff change.diff \
++ --target settings.py::accepts \
++ --target service.py::Service.apply \
++ --script responses --out out/selected
++~~~
++
++Use the same repeatable option with `--git-base REF`, or `--diff -` for stdin.
++A diff input remains required; an empty diff is permitted when you deliberately
++want tests for current functions without a recorded code change.
++
++## Selection and preview
++
++With `--target`, TestPilot selects exactly the requested current functions in
++the order supplied. It does not add automatically changed functions. Repeating
++the same resolved source/function identity selects it once, at its first
++position. The `selection.requested` record preserves every original argument,
++including repetitions and spelling, while function records use normalized
++repository-relative paths.
++
++Use a repository-relative Python file followed by `::` and its exact qualified
++name. A method is `file.py::Class.method`; nested classes can add more components.
++The existing AST discovery also supports async functions and definitions inside
++module/class `if`, `try`, `with` and `match` blocks. Nested functions belong to
++their enclosing function and cannot be selected separately. The final `::`
++separates the path from the name; quote the complete argument in your shell when
++the path needs it.
++
++A name must identify one source definition. Multiple definitions with the same
++qualified name are refused, including definitions in mutually exclusive branches
++or property getter/setter definitions. TestPilot does not guess which definition
++will exist at runtime.
++
++Missing files/names, malformed specs, non-Python and test sources, absolute or
++parent-traversing paths, and symlinks resolving outside the repository are
++refused before baseline tests or model calls. A normal-looking alias to a test
++source is also refused. Inside-repository source symlinks follow the existing
++containment rule. Python source is decoded with Python's encoding-cookie/BOM
++rules; selecting source never imports or executes it.
++
++The existing `targets` preview remains read-only: no tests, generated files,
++model calls or output directories. Text output says "caller-selected"; `--json`
++includes the exact selected source records plus the selection context. A preview
++does not reserve files: `run` reads the current source again.
++
++## Context and saved results
++
++Explicit mode sends the exact current selected function source and the exact
++diff string received by `TestPilot.run` to the planner, editor and every repair
++call. It identifies the functions as caller-selected and potentially unchanged.
++The CLI retains its existing raw-byte diff loader, including universal-newline
++normalization and surrogate escaping; recorded `diff_text` is that loaded
++string, not a reconstruction or a byte-for-byte copy of the original diff file.
++
++The existing `changed_functions` JSON field remains the selected native source
++record list for compatibility. Explicit previews and run reports additionally
++include:
++
++~~~json
++{
++ "selection": {
++ "mode": "explicit",
++ "requested": ["settings.py::accepts"],
++ "reason": "Caller selected these targets; no dependency inference was performed.",
++ "diff_text": ""
++ }
++}
++~~~
++
++Markdown labels caller-selected functions and the reason. The offline HTML view
++also shows the reason and an expandable literal diff context. Its original JSON
++download retains the complete selection record.
++
++Without `--target` (or with the Python API's default `targets=None`), the existing
++automatic selector, prompts, report/preview schema and output remain unchanged.
++The optional `selection` object is absent. Python callers can use
++`TestPilot.run(repo, diff_text, targets=["path.py::qualname"])`; an empty explicit
++sequence is an error. `testpilot.targets.resolve_targets(repo, diff_text, targets)`
++provides the same resolver, raising `TargetSelectionError` on a refused request.
++
++## Coverage and limits
++
++`changed_lines` still contains only actual added diff lines inside each selected
++function's current span. An unchanged function therefore has an empty list.
++Selecting it does not turn all of its lines into changed lines. Total coverage
++can improve, while the changed-line population is zero and its percentages stay
++unavailable. Unrelated or refused diff source paths are context only in explicit
++mode and are never opened or added as targets.
++
++This is a deliberate selection workflow. It does not infer dependencies, prove
++that all affected functions were selected, or supply surrounding module values
++that are absent from both the selected source and the supplied diff. A developer
++must provide useful context and review the resulting tests. The existing
++sandbox, generated-file preservation, model routing, token budget, execution and
++patch contracts apply unchanged.
++
++Receiving uses deterministic `ScriptedModel` responses with actual outgoing
++message checks, subprocess tests and patch application. Those controls qualify
++the workflow and context delivery; they do not establish live-model generation
++quality.
+diff --git a/testpilot/__main__.py b/testpilot/__main__.py
+index 2d98e2a..5686cb4 100644
+--- a/testpilot/__main__.py
++++ b/testpilot/__main__.py
+@@ -13,6 +13,7 @@ from .diff import DiffSourceError, changed_functions
+ from .loop import TestPilot, render_report, write_outputs
+ from .model import ModelError, RoutingConfig, make_client
+ from .recheck import run_recheck_command
++from .targets import TargetSelectionError, resolve_targets, selection_record
+
+
+ def _python_executable(value: str) -> str:
+@@ -48,16 +49,24 @@ def _preview_targets(args) -> int:
+ """Print the native selector's result without starting generation or tests."""
+ try:
+ diff_text = _read_diff(args)
+- functions = changed_functions(args.repo, diff_text)
++ target_specs = getattr(args, "target", None)
++ functions = (changed_functions(args.repo, diff_text) if target_specs is None
++ else resolve_targets(args.repo, diff_text, target_specs))
+ except (OSError, ValueError, SyntaxError, subprocess.CalledProcessError) as exc:
+ print(f"testpilot: cannot inspect targets: {exc}", file=sys.stderr)
+ return 2
+
+ if args.json:
+- print(json.dumps({"changed_functions": [f.to_dict() for f in functions]}, indent=2))
++ payload = {"changed_functions": [f.to_dict() for f in functions]}
++ if target_specs is not None:
++ payload["selection"] = selection_record(diff_text, target_specs)
++ print(json.dumps(payload, indent=2))
+ else:
+ noun = "function" if len(functions) == 1 else "functions"
+- print(f"{len(functions)} changed Python {noun} outside tests")
++ qualifier = "changed" if target_specs is None else "caller-selected"
++ print(f"{len(functions)} {qualifier} Python {noun} outside tests")
++ if target_specs is not None:
++ print(selection_record(diff_text, target_specs)["reason"])
+ for function in functions:
+ path = json.dumps(function.path)
+ name = json.dumps(function.qualname)
+@@ -83,6 +92,8 @@ def main(argv: list[str] | None = None) -> int:
+ help="project Python for all test runs; path or PATH command (default: this interpreter)")
+ r.add_argument("--max-tokens", type=int, default=None, help="total token budget for the run")
+ r.add_argument("--out", default="testpilot-out")
++ r.add_argument("--target", action="append", metavar="PATH.py::QUALNAME",
++ help="select this function explicitly; repeat for caller order, instead of automatic diff selection")
+
+ preview = sub.add_parser("targets", help="inspect changed functions without generating or running tests")
+ preview.add_argument("--repo", required=True)
+@@ -90,6 +101,8 @@ def main(argv: list[str] | None = None) -> int:
+ source.add_argument("--diff", help="unified diff file ('-' for stdin)")
+ source.add_argument("--git-base", help="diff the working tree against this git ref")
+ preview.add_argument("--json", action="store_true", help="include full native target records and source")
++ preview.add_argument("--target", action="append", metavar="PATH.py::QUALNAME",
++ help="inspect this explicit function; repeat instead of automatic diff selection")
+ recheck = sub.add_parser("recheck", help="rerun exact tests from a saved report without model calls")
+ recheck.add_argument("--repo", required=True, help="current project checkout")
+ recheck.add_argument("--report", required=True, help="saved report.json or recheck.json")
+@@ -106,6 +119,12 @@ def main(argv: list[str] | None = None) -> int:
+ return _preview_targets(a)
+
+ diff_text = _read_diff(a)
++ if a.target is not None:
++ try:
++ resolve_targets(a.repo, diff_text, a.target)
++ except TargetSelectionError as e:
++ print(f"testpilot: invalid target selection: {e}", file=sys.stderr)
++ return 2
+ try:
+ client = make_client(a.backend, script=a.script, base_url=a.base_url)
+ except ModelError as e:
+@@ -114,7 +133,11 @@ def main(argv: list[str] | None = None) -> int:
+ pilot = TestPilot(client, RoutingConfig.from_env(), max_repair_rounds=a.rounds, timeout_s=a.timeout,
+ max_total_tokens=a.max_tokens, python=a.python)
+ try:
+- res = pilot.run(a.repo, diff_text)
++ res = (pilot.run(a.repo, diff_text) if a.target is None
++ else pilot.run(a.repo, diff_text, targets=a.target))
++ except TargetSelectionError as e:
++ print(f"testpilot: invalid target selection: {e}", file=sys.stderr)
++ return 2
+ except DiffSourceError as e:
+ print(f"testpilot: invalid diff source: {e}", file=sys.stderr)
+ return 2
+diff --git a/testpilot/html_report.py b/testpilot/html_report.py
+index bb30d90..2fcc644 100644
+--- a/testpilot/html_report.py
++++ b/testpilot/html_report.py
+@@ -277,6 +277,10 @@ def render_html_report(report_json: bytes, patch: bytes) -> str:
+ 'A model response without a new run does not add an execution. '
+ + _rounds(report['rounds']) + '')
+ parts.append('Plan and selected source ')
++ selection = report.get("selection")
++ if isinstance(selection, dict) and selection.get("mode") == "explicit":
++ parts.append('' + _text(selection.get("reason", "Caller-selected targets.")) + '
')
++ parts.append(_disclosure("Supplied diff context", _pre(selection.get("diff_text", ""))))
+ if report.get('plan'):
+ parts.append('Model test plan ' + _pre(report['plan']))
+ else:
+diff --git a/testpilot/loop.py b/testpilot/loop.py
+index 3057a9c..8a567fb 100644
+--- a/testpilot/loop.py
++++ b/testpilot/loop.py
+@@ -18,6 +18,7 @@ from __future__ import annotations
+ import difflib
+ import json
+ import re
++from collections.abc import Sequence
+ from dataclasses import asdict, dataclass, field
+ from pathlib import Path, PurePosixPath
+
+@@ -25,6 +26,7 @@ from .diff import ChangedFunction, changed_functions
+ from .html_report import render_html_report
+ from .model import ChatClient, ModelError, RoutingConfig, Usage
+ from .sandbox import IGNORE, SandboxResult, run_pytest
++from .targets import resolve_targets, selection_record
+
+ SYSTEM_PLANNER = (
+ "You are TestPilot's planner. Given changed Python functions from a pull request, "
+@@ -357,6 +359,7 @@ class LoopResult:
+ rounds: list[RoundRecord]
+ plan: str = ""
+ message: str = ""
++ selection: dict | None = None
+
+ @property
+ def ok(self) -> bool:
+@@ -364,6 +367,8 @@ class LoopResult:
+
+ def to_dict(self) -> dict:
+ d = asdict(self)
++ if self.selection is None:
++ d.pop("selection")
+ d["ok"] = self.ok
+ return d
+
+@@ -401,10 +406,23 @@ class TestPilot:
+ def _run(self, repo: Path, files: dict[str, str] | None) -> SandboxResult:
+ return run_pytest(repo, files, timeout=self.timeout_s, coverage=self.coverage, python=self.python)
+
+- def run(self, repo: str | Path, diff_text: str) -> LoopResult:
++ def run(self, repo: str | Path, diff_text: str, *,
++ targets: Sequence[str] | None = None) -> LoopResult:
+ repo = Path(repo)
+ ledger = Ledger(self.routing, self.max_total_tokens)
+- funcs = changed_functions(repo, diff_text)
++ selection = selection_record(diff_text, targets) if targets is not None else None
++ funcs = (changed_functions(repo, diff_text) if selection is None
++ else resolve_targets(repo, diff_text, selection["requested"]))
++ context = f"Changed functions:\n\n{_functions_block(funcs)}"
++ planner_system, editor_system, repair_system = SYSTEM_PLANNER, SYSTEM_EDITOR, SYSTEM_REPAIR
++ if selection is not None:
++ context = (f"{selection['reason']}\n\nCaller-selected functions:\n\n{_functions_block(funcs)}"
++ f"\n\nSupplied diff context:\n```diff\n{diff_text}\n```")
++ note = " These functions were explicitly chosen and may be unchanged."
++ planner_system = SYSTEM_PLANNER.replace("changed Python functions from a pull request",
++ "caller-selected Python functions and a supplied diff") + note
++ editor_system = SYSTEM_EDITOR.replace("the changed functions", "the caller-selected functions") + note
++ repair_system = SYSTEM_REPAIR + note
+ rounds: list[RoundRecord] = []
+ files: dict[str, str] = {}
+ aliases: dict[str, str] = {}
+@@ -428,18 +446,17 @@ class TestPilot:
+ final=final.to_dict() if final else None,
+ patch=patch,
+ coverage=coverage_delta(baseline, final, funcs), ledger=ledger.to_dict(), rounds=rounds,
+- plan=plan, message=msg)
++ plan=plan, message=msg, selection=selection)
+
+ if not funcs:
+ return result("no_changes", "diff touches no Python functions outside tests")
+
+ baseline = self._run(repo, None)
+ try:
+- plan = self._ask(ledger, "planner", SYSTEM_PLANNER,
+- f"Changed functions:\n\n{_functions_block(funcs)}\n\nWrite the test plan.")
+- reply = self._ask(ledger, "editor", SYSTEM_EDITOR,
+- f"Test plan:\n{plan}\n\nChanged functions:\n\n{_functions_block(funcs)}\n\n"
+- "Write the pytest file(s).")
++ plan = self._ask(ledger, "planner", planner_system,
++ f"{context}\n\nWrite the test plan.")
++ reply = self._ask(ledger, "editor", editor_system,
++ f"Test plan:\n{plan}\n\n{context}\n\nWrite the pytest file(s).")
+ proposed, warns = parse_test_files(reply)
+ if not proposed:
+ rounds.append(RoundRecord(0, "generate", [], None, warns + ["no python code block in reply"]))
+@@ -451,8 +468,8 @@ class TestPilot:
+
+ while not final.ok and repairs < self.max_repair_rounds:
+ repairs += 1
+- reply = self._ask(ledger, "repair", SYSTEM_REPAIR,
+- f"Changed functions:\n\n{_functions_block(funcs)}\n\nCurrent tests:\n\n"
++ reply = self._ask(ledger, "repair", repair_system,
++ f"{context}\n\nCurrent tests:\n\n"
+ f"{_files_block(files)}\n\nFailure report:\n{final.failure_report()}")
+ verdict = parse_verdict(reply)
+ if verdict:
+@@ -493,7 +510,10 @@ def render_report(res: LoopResult) -> str:
+ lines = [f"# TestPilot report: **{res.status}**", ""]
+ if res.message:
+ lines += [res.message, ""]
+- lines.append(f"- Changed functions: {', '.join(f['path'] + '::' + f['qualname'] for f in res.changed_functions) or 'none'}")
++ label = "Changed functions" if res.selection is None else "Caller-selected functions"
++ lines.append(f"- {label}: {', '.join(f['path'] + '::' + f['qualname'] for f in res.changed_functions) or 'none'}")
++ if res.selection is not None:
++ lines.append(f"- Selection: {res.selection['reason']}")
+ lines.append(f"- Tests written: {res.tests_written} in {', '.join(sorted(res.test_files)) or '-'}")
+ lines.append(f"- Repair rounds used: {res.repair_rounds_used}/{res.max_repair_rounds}")
+ if res.final:
+diff --git a/testpilot/targets.py b/testpilot/targets.py
+new file mode 100644
+index 0000000..2da1600
+--- /dev/null
++++ b/testpilot/targets.py
+@@ -0,0 +1,120 @@
++"""Resolve caller-chosen Python functions without inferring dependency impact."""
++from __future__ import annotations
++
++import ast
++import tokenize
++from collections.abc import Sequence
++from pathlib import Path
++
++from .diff import (
++ ChangedFunction,
++ DiffSourceError,
++ _iter_functions,
++ _source_path,
++ functions_touching,
++ is_test_path,
++ parse_unified_diff,
++)
++
++SELECTION_REASON = "Caller selected these targets; no dependency inference was performed."
++
++
++class TargetSelectionError(ValueError):
++ """An explicit target cannot identify one permitted current function."""
++
++
++def _requested_specs(targets: Sequence[str]) -> list[str]:
++ if isinstance(targets, (str, bytes)) or not isinstance(targets, Sequence):
++ raise TargetSelectionError("targets must be a nonempty sequence of path.py::qualname strings")
++ requested = list(targets)
++ if not requested or any(not isinstance(spec, str) for spec in requested):
++ raise TargetSelectionError("targets must be a nonempty sequence of path.py::qualname strings")
++ return requested
++
++
++def selection_record(diff_text: str, targets: Sequence[str]) -> dict:
++ """Snapshot caller intent; automatic runs omit this record entirely."""
++ if not isinstance(diff_text, str):
++ raise TargetSelectionError("the supplied diff must be text")
++ return {
++ "mode": "explicit",
++ "requested": _requested_specs(targets),
++ "reason": SELECTION_REASON,
++ "diff_text": diff_text,
++ }
++
++
++def resolve_targets(repo: str | Path, diff_text: str,
++ targets: Sequence[str]) -> list[ChangedFunction]:
++ """Select exactly named current functions, deduplicated in caller order.
++
++ The existing AST discovery excludes nested functions and includes methods
++ and module/class control-flow definitions. A repeated qualified name in one
++ source file is ambiguous, even when only one branch executes at runtime.
++ No source module is imported or executed during selection.
++ """
++ requested = _requested_specs(targets)
++ root = Path(repo).resolve()
++ if not root.is_dir():
++ raise TargetSelectionError(f"repository is not a directory: {str(repo)!r}")
++ try:
++ changes = parse_unified_diff(diff_text)
++ except (TypeError, ValueError) as exc:
++ raise TargetSelectionError(f"cannot parse supplied diff: {exc}") from exc
++
++ # Resolve identities only: unrelated diff sources are never opened. A diff
++ # remains context in explicit mode, and cannot add targets to the request.
++ added_by_source: dict[Path, set[int]] = {}
++ for change in changes:
++ if change.is_deleted or not change.path.endswith(".py") or is_test_path(change.path):
++ continue
++ try:
++ source_path = _source_path(root, change.path)
++ except DiffSourceError:
++ continue
++ added_by_source.setdefault(source_path, set()).update(change.added_lines)
++
++ sources: dict[Path, str] = {}
++ functions_by_path: dict[str, list[ChangedFunction]] = {}
++ selected: list[ChangedFunction] = []
++ seen: set[tuple[Path, str, int, int]] = set()
++ for spec in requested:
++ raw_path, separator, qualname = spec.rpartition("::")
++ if (not separator or not raw_path or not qualname
++ or any(not part.isidentifier() for part in qualname.split("."))):
++ raise TargetSelectionError(f"expected path.py::qualname: {spec!r}")
++ path = Path(raw_path).as_posix()
++ if not path.endswith(".py") or is_test_path(path):
++ raise TargetSelectionError(f"target must be a Python source file outside tests: {spec!r}")
++ try:
++ source_path = _source_path(root, raw_path)
++ resolved_path = source_path.relative_to(root).as_posix()
++ if not resolved_path.endswith(".py") or is_test_path(resolved_path):
++ raise TargetSelectionError(f"target resolves to a non-Python or test source: {spec!r}")
++ if not source_path.is_file():
++ raise TargetSelectionError(f"target source does not exist: {spec!r}")
++ if source_path not in sources:
++ with tokenize.open(source_path) as source_file:
++ sources[source_path] = source_file.read()
++ if path not in functions_by_path:
++ source = sources[source_path]
++ tree = ast.parse(source, filename=path)
++ starts = {node.lineno for _, node, _ in _iter_functions(tree.body)}
++ functions_by_path[path] = functions_touching(
++ source, path, starts, added_by_source.get(source_path, set()))
++ except TargetSelectionError:
++ raise
++ except (OSError, ValueError, SyntaxError, LookupError) as exc:
++ raise TargetSelectionError(f"cannot inspect target {spec!r}: {exc}") from exc
++
++ matches = [function for function in functions_by_path[path] if function.qualname == qualname]
++ if not matches:
++ raise TargetSelectionError(f"no selectable function matches {spec!r}")
++ if len(matches) != 1:
++ raise TargetSelectionError(f"ambiguous target matches {len(matches)} definitions: {spec!r}")
++ function = matches[0]
++ identity = (source_path, function.qualname, function.lineno, function.end_lineno)
++ if identity not in seen:
++ selected.append(function)
++ seen.add(identity)
++ return selected
+diff --git a/tests/test_explicit_target_cli.py b/tests/test_explicit_target_cli.py
+new file mode 100644
+index 0000000..d01e0da
+--- /dev/null
++++ b/tests/test_explicit_target_cli.py
+@@ -0,0 +1,41 @@
++import json
++import os
++import subprocess
++import sys
++from pathlib import Path
++
++from testpilot.__main__ import main
++
++ROOT = Path(__file__).resolve().parents[1]
++SOURCE = "LIMIT = 50\n\n\ndef accepts(amount):\n return amount <= LIMIT\n"
++DIFF = "--- a/settings.py\n+++ b/settings.py\n@@ -1 +1 @@\n-LIMIT = 100\n+LIMIT = 50\n"
++
++
++def test_real_preview_default_and_explicit_stdin(tmp_path):
++ (tmp_path / "settings.py").write_text(SOURCE)
++ env = {**os.environ, "PYTHONDONTWRITEBYTECODE": "1"}
++ command = [sys.executable, "-B", "-m", "testpilot", "targets",
++ "--repo", str(tmp_path), "--diff", "-", "--json"]
++ automatic = subprocess.run(command, input=DIFF, cwd=ROOT, env=env,
++ text=True, capture_output=True, check=True)
++ assert json.loads(automatic.stdout) == {"changed_functions": []}
++ explicit = subprocess.run(command + ["--target", "settings.py::accepts"],
++ input=DIFF, cwd=ROOT, env=env, text=True,
++ capture_output=True, check=True)
++ payload = json.loads(explicit.stdout)
++ assert payload["selection"]["diff_text"] == DIFF
++ assert payload["changed_functions"][0]["source"] == "def accepts(amount):\n return amount <= LIMIT"
++ assert list(tmp_path.iterdir()) == [tmp_path / "settings.py"]
++
++
++def test_run_refuses_target_before_constructing_model(tmp_path, monkeypatch, capsys):
++ diff = tmp_path / "change.diff"
++ diff.write_text(DIFF)
++ def fail_client(*args, **kwargs):
++ raise AssertionError("invalid target constructed a model client")
++ monkeypatch.setattr("testpilot.__main__.make_client", fail_client)
++ output = tmp_path / "out"
++ assert main(["run", "--repo", str(tmp_path), "--diff", str(diff),
++ "--target", "settings.py::missing", "--out", str(output)]) == 2
++ assert "invalid target selection" in capsys.readouterr().err
++ assert not output.exists()
+diff --git a/tests/test_explicit_targets.py b/tests/test_explicit_targets.py
+new file mode 100644
+index 0000000..438d1c2
+--- /dev/null
++++ b/tests/test_explicit_targets.py
+@@ -0,0 +1,102 @@
++import json
++
++import pytest
++
++from testpilot.diff import changed_functions
++from testpilot.html_report import render_html_report
++from testpilot.loop import TestPilot, render_report
++from testpilot.model import ScriptedModel
++from testpilot.targets import SELECTION_REASON, TargetSelectionError, resolve_targets
++
++SOURCE = "LIMIT = 50\n\n\ndef accepts(amount):\n return amount <= LIMIT\n\n\ndef other():\n return 2\n"
++DIFF = "--- a/settings.py\n+++ b/settings.py\n@@ -1 +1 @@\n-LIMIT = 100\n+LIMIT = 50\n"
++FENCE = chr(96) * 3
++GOOD = (FENCE + "python path=tests/test_selected.py\nfrom settings import accepts\n\n\n"
++ "def test_accepts():\n assert accepts(50)\n assert not accepts(75)\n" + FENCE)
++BAD = GOOD.replace("assert not accepts(75)", "assert accepts(75)")
++
++
++def test_current_selection_order_and_real_added_lines(tmp_path):
++ (tmp_path / "settings.py").write_text(SOURCE)
++ assert changed_functions(tmp_path, DIFF) == []
++ chosen = resolve_targets(tmp_path, DIFF, [
++ "settings.py::other", "./settings.py::accepts", "settings.py::other"])
++ assert [f.qualname for f in chosen] == ["other", "accepts"]
++ assert [f.changed_lines for f in chosen] == [[], []]
++ assert chosen[1].path == "settings.py"
++ assert chosen[1].source == "def accepts(amount):\n return amount <= LIMIT"
++ changed = DIFF + "@@ -9 +9 @@\n- return 1\n+ return 2\n"
++ assert resolve_targets(tmp_path, changed, ["settings.py::other"])[0].changed_lines == [9]
++
++
++def test_native_discovery_encoding_and_ambiguity(tmp_path):
++ source = ("# coding: latin-1\nclass Café:\n @staticmethod\n"
++ " def réponse():\n return 'déjà'\n")
++ (tmp_path / "encoded.py").write_bytes(source.encode("latin-1"))
++ function = resolve_targets(tmp_path, "", ["encoded.py::Café.réponse"])[0]
++ assert function.is_method and function.lineno == 3 and function.end_lineno == 5
++ assert function.source == " @staticmethod\n def réponse():\n return 'déjà'"
++ (tmp_path / "branches.py").write_text(
++ "if True:\n def selected(): return 1\nelse:\n def selected(): return 2\n")
++ with pytest.raises(TargetSelectionError, match="ambiguous"):
++ resolve_targets(tmp_path, "", ["branches.py::selected"])
++
++
++@pytest.mark.parametrize("spec", ["settings.py", "settings.py::missing", "settings.py::accepts.inner",
++ "../outside.py::f", "tests/test_sample.py::f"])
++def test_invalid_requests_refuse_without_execution(tmp_path, spec):
++ (tmp_path / "settings.py").write_text(SOURCE)
++ (tmp_path / "tests").mkdir()
++ (tmp_path / "tests" / "test_sample.py").write_text("raise RuntimeError('never import')\ndef f(): pass\n")
++ client = ScriptedModel([])
++ pilot = TestPilot(client, coverage=False)
++ pilot._run = lambda *args: pytest.fail("invalid target reached test execution")
++ with pytest.raises(TargetSelectionError):
++ pilot.run(tmp_path, DIFF, targets=[spec])
++ assert client.calls == []
++
++
++def test_aliases_cannot_select_test_sources(tmp_path):
++ (tmp_path / "tests").mkdir()
++ target = tmp_path / "tests" / "helper.py"
++ target.write_text("def f(): pass\n")
++ (tmp_path / "alias.py").symlink_to(target)
++ with pytest.raises(TargetSelectionError, match="test source"):
++ resolve_targets(tmp_path, "", ["alias.py::f"])
++
++
++def test_actual_context_repair_and_saved_selection(tmp_path):
++ repo = tmp_path / "repo"
++ repo.mkdir()
++ (repo / "settings.py").write_text(SOURCE)
++ client = ScriptedModel(["Test the supplied new limit of 50.", BAD, GOOD])
++ specs = ["settings.py::accepts"]
++ result = TestPilot(client, max_repair_rounds=1, timeout_s=30, coverage=False).run(
++ repo, DIFF, targets=specs)
++ assert result.status == "passed" and result.tests_written == 1 and result.repair_rounds_used == 1
++ assert result.changed_functions[0]["changed_lines"] == []
++ assert len(client.calls) == 3
++ for _, messages in client.calls:
++ assert "explicitly chosen" in messages[0]["content"]
++ assert SELECTION_REASON in messages[1]["content"]
++ assert DIFF in messages[1]["content"]
++ assert result.changed_functions[0]["source"] in messages[1]["content"]
++ assert "def other" not in messages[1]["content"]
++ assert "assert False" in client.calls[2][1][1]["content"]
++ specs.append("settings.py::other")
++ assert result.to_dict()["selection"]["requested"] == ["settings.py::accepts"]
++ assert result.to_dict()["selection"]["diff_text"] == DIFF
++ assert "Caller-selected functions: settings.py::accepts" in render_report(result)
++ html = render_html_report(json.dumps(result.to_dict()).encode(), result.patch.encode())
++ assert SELECTION_REASON in html and "Supplied diff context" in html
++ assert not (repo / "tests").exists()
++ assert (repo / "settings.py").read_text() == SOURCE
++
++
++def test_automatic_no_change_keeps_original_schema(tmp_path):
++ (tmp_path / "settings.py").write_text(SOURCE)
++ client = ScriptedModel([])
++ result = TestPilot(client).run(tmp_path, DIFF)
++ assert result.status == "no_changes" and result.changed_functions == []
++ assert "selection" not in result.to_dict() and client.calls == []
++ assert "- Changed functions: none" in render_report(result)
+--
+2.50.1 (Apple Git-155)
+
diff --git a/docs/receiving/explicit-targets-a219f250962c/source/frozen-source.json b/docs/receiving/explicit-targets-a219f250962c/source/frozen-source.json
new file mode 100644
index 0000000..074d7aa
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/source/frozen-source.json
@@ -0,0 +1,203 @@
+{
+ "repository": "Jacob-Met/testpilot",
+ "source_path": "/Users/me/hamon-testpilot-explicit-targets-a219f250962c",
+ "code_commit": "00b63c8e03cc515160da1202331224167d12d253",
+ "tree": "d1ba76e3872a0e843821b799f8accb69aa32416d",
+ "canonical_base": "63174afd8e63a0ea562cf188b7d971522f017a30",
+ "canonical_base_tree": "cea947d9aeb2253466593044889a1bb548f8b7c4",
+ "scoped_base": "33a10cd5b756b8ade8768bd6f8da75f55da369c3",
+ "scoped_base_tree": "a011210502f9171c1b6cffff76342b13fb755b85",
+ "authored_previous_commit": "fc24a55d2c5e2d94478072e8e6736bf44bab81a6",
+ "authored_previous_tree": "6b2582c2041bfa9a7cb3600bcead12574f6230c9",
+ "author_paths": [
+ "README.md",
+ "docs/EXPLICIT_TARGETS.md",
+ "testpilot/__main__.py",
+ "testpilot/html_report.py",
+ "testpilot/loop.py",
+ "testpilot/targets.py",
+ "tests/test_explicit_target_cli.py",
+ "tests/test_explicit_targets.py"
+ ],
+ "identical_author_paths_from_previous": [
+ "testpilot/targets.py",
+ "testpilot/loop.py",
+ "testpilot/html_report.py",
+ "README.md",
+ "docs/EXPLICIT_TARGETS.md",
+ "tests/test_explicit_targets.py",
+ "tests/test_explicit_target_cli.py"
+ ],
+ "unchanged_current_parent_files": 19,
+ "files": [
+ {
+ "path": "README.md",
+ "bytes": 12053,
+ "sha256": "9c646cbd14a07a5af79afd9a306202d402e2037db7e6557525d9e18387e27f5a",
+ "git_blob": "3848e40614d7328eb5544a28f52cd6a75de38aa1"
+ },
+ {
+ "path": "docs/EXPLICIT_TARGETS.md",
+ "bytes": 5497,
+ "sha256": "dabf34adc34d931749f04debe601fb8f2c6a0ab668278c8539926fb05c88cca8",
+ "git_blob": "3cfe7a0370d48a1e86c6f7c49bb7508fbdfb8abd"
+ },
+ {
+ "path": "docs/RECHECK.md",
+ "bytes": 5885,
+ "sha256": "7855d34a1e84a4fb4b4ef113aa4bc488ccf28933883623e099ae427cc0fdbfc3",
+ "git_blob": "b7eff4e6089c2974b8c277370f474935d4bf4806"
+ },
+ {
+ "path": "pyproject.toml",
+ "bytes": 676,
+ "sha256": "5503eab7b6c145d4de241e5be805a4fb8f30828053cbe39dc05a43c2896642eb",
+ "git_blob": "952255ac87e2c7da57457fdbf3554914f17f6d05"
+ },
+ {
+ "path": "testpilot/__init__.py",
+ "bytes": 390,
+ "sha256": "65c63b80d7da34a83a6049a79c24b8335878cd49cc79951fdefc5f1532ae841f",
+ "git_blob": "296210dd0d58703de4a4e4c9ad72a0944c9400ca"
+ },
+ {
+ "path": "testpilot/__main__.py",
+ "bytes": 7479,
+ "sha256": "319bc6e8b06615d657530eb35fa2f7f3e6a883a36c98ea1540fdf4c2815eac55",
+ "git_blob": "5686cb41d555890b906d2ea322149f0673e8049d"
+ },
+ {
+ "path": "testpilot/_pytest_generated.py",
+ "bytes": 2943,
+ "sha256": "d65352d7bade965d7b9dc73efe5d4d01844fa8c7474a1bdcdb3b708f6decf768",
+ "git_blob": "90b82725bc0eb1ff3d7cb517c4c9dedcfd14bdcc"
+ },
+ {
+ "path": "testpilot/diff.py",
+ "bytes": 9396,
+ "sha256": "659021d4a4b462b86eadc25957407b683c733fec60ae33c0703295cb7640a06c",
+ "git_blob": "f92280a9ad3f9f79713dce7ba7ae28c55414783a"
+ },
+ {
+ "path": "testpilot/html_report.py",
+ "bytes": 20982,
+ "sha256": "f2b65ee9201f9e91ad183fcb8f6b8bf6e69c77b1a066fcd90bb431951e41a2f6",
+ "git_blob": "2fcc6441473684c2f8bdd16d314e8b258de51edf"
+ },
+ {
+ "path": "testpilot/loop.py",
+ "bytes": 25406,
+ "sha256": "3ea786ae5466ebdd6087b03d829580c333882e6fd429aaf54099320b4aa0bb08",
+ "git_blob": "8a567fb35f29dc242413df3678f5f63fe08caa4f"
+ },
+ {
+ "path": "testpilot/model.py",
+ "bytes": 10175,
+ "sha256": "f6ae697f1f5d686ffdcb96f5451d1a9600d7d41c7bcf324d7e8c276a107e8673",
+ "git_blob": "6d4abca38cc1c256df00bcac7923e2dc0bd42b16"
+ },
+ {
+ "path": "testpilot/recheck.py",
+ "bytes": 11599,
+ "sha256": "1ecbe0ff7da7bb4a8117d730a76624c8929e0e4b10cb9d502ab7358cb38d8f2e",
+ "git_blob": "4ab26cbb83beff563b9c3683c593b3efd2495b6b"
+ },
+ {
+ "path": "testpilot/sandbox.py",
+ "bytes": 12721,
+ "sha256": "8d5aadafff5188473ec45eb1cd8f01fb562836ff05bf12a27939cde83b33472a",
+ "git_blob": "8ca4a4102c7890e89eae99548e28ba8325974f1e"
+ },
+ {
+ "path": "testpilot/targets.py",
+ "bytes": 5410,
+ "sha256": "35add23585b0e88246288572f49bd857ebf4af83a221b9129e784e9b456c1ab0",
+ "git_blob": "2da1600eca93edbe47605e6ca2a9e715663fa370"
+ },
+ {
+ "path": "tests/conftest.py",
+ "bytes": 144,
+ "sha256": "0cb67ba91674c07e557440292720aa3c3c111f4c41b7db1f4d219a7c68dc6f52",
+ "git_blob": "2a855d9fdb8debae0b082088580a4c41e499e33c"
+ },
+ {
+ "path": "tests/test_cli_diff_transport.py",
+ "bytes": 11507,
+ "sha256": "5748cd3aafbd5aa032b98d531a9091d4a91becdce187a514dafffcc271173bab",
+ "git_blob": "25017791488cf8dd3c7f95b9460d73d691ce8a06"
+ },
+ {
+ "path": "tests/test_cli_targets.py",
+ "bytes": 10853,
+ "sha256": "f53a8a7ed6ac752e5bfc1e32a92c88d5985e1da2a8296f317df2ea5b11f5c082",
+ "git_blob": "281550909a8cce1067983199c5122e1bc5c0b78e"
+ },
+ {
+ "path": "tests/test_diff_source_boundary.py",
+ "bytes": 5521,
+ "sha256": "f8442bb2e26a4cbdfa7b6320627c780a83264675959ddc6339bc88b57a6faec2",
+ "git_blob": "74f9ca4d4a31b9ca447f362ac3cbf594333d560f"
+ },
+ {
+ "path": "tests/test_explicit_target_cli.py",
+ "bytes": 1930,
+ "sha256": "01eb0a6782448a59bbf876fa1d8cbf8398bb05b461f5875b4d125b0147ab3365",
+ "git_blob": "d01e0dad0cb73fe1f54b6c88d8287792dd587869"
+ },
+ {
+ "path": "tests/test_explicit_targets.py",
+ "bytes": 5268,
+ "sha256": "bd40f190a52195eed73145cbd0a721b0c555307dfd358ec1d795df3799026b1d",
+ "git_blob": "438d1c23335d79d4151a198f7e3606595cd68785"
+ },
+ {
+ "path": "tests/test_fenced_code_boundaries.py",
+ "bytes": 2990,
+ "sha256": "edff7f6dec6c754cdbd03efee84b342103bb1c43a8b614333e1ca83e75bfd91c",
+ "git_blob": "54c798f49b690d6365ec4de9b0ae78f9d99cd960"
+ },
+ {
+ "path": "tests/test_html_report.py",
+ "bytes": 9811,
+ "sha256": "bb2d6b66ecf929ee6340ae6f39875a07e64dd8c5af2053944b16ad93aa7d6f5c",
+ "git_blob": "655c4ce97b5ffbd2fe81f5b602a2ca12388c27b7"
+ },
+ {
+ "path": "tests/test_loop.py",
+ "bytes": 5465,
+ "sha256": "efcf833870d9fb407d324c48d8421d587089407b4b1db9f7ba24fad7c2ed67dc",
+ "git_blob": "8540b31caf7c258e9345e62d11cd4796c5e92ba5"
+ },
+ {
+ "path": "tests/test_patch_physical_lines.py",
+ "bytes": 3085,
+ "sha256": "8318fb4e20f87e4f0cc25013aab9f18b3a4236537bbae5f2fbb7e56a1b2a825b",
+ "git_blob": "f5a9ff15106d39c953cda3f783f64362634f7ad1"
+ },
+ {
+ "path": "tests/test_python_source_encoding.py",
+ "bytes": 5598,
+ "sha256": "85196e144916098af0fe677f01382eeb48c6f175008c76446756c438ee741b89",
+ "git_blob": "903abd6db569a80e3a5a61d734bf376c0ac4c8dd"
+ },
+ {
+ "path": "tests/test_recheck.py",
+ "bytes": 13652,
+ "sha256": "dc74c36ddc2f2bd62505549648d0572e5a388840076c8965338f23d371173760",
+ "git_blob": "6ec7e36cd054d320842b5cdcfd34b635d4dc64f4"
+ },
+ {
+ "path": "tests/test_report_text_encoding.py",
+ "bytes": 9330,
+ "sha256": "1dde3e4f3b227918e541e4ffc71099a8f36c235c198169bb1a152302ea635a3e",
+ "git_blob": "44e7622672361ffad936e462b0996816c55d2ddd"
+ }
+ ],
+ "receiving_scope": "Source/CLI/prompt/sandbox/patch receiving pending independent root disposition; baseline-matched native existing-test limitations retained",
+ "native_results": {
+ "old_e228": "126 passed,18 subtests passed,2 existing filename fixture failures reproduced on untouched baseline",
+ "current_631": "58 passed,18 subtests passed,1 existing skip,1 existing configured-suite expected-count failure reproduced on untouched baseline",
+ "new_tests": "All12 new explicit-target tests pass in both source compositions"
+ },
+ "clean": true
+}
diff --git a/docs/receiving/explicit-targets-a219f250962c/source/original-baseline-manifest.json b/docs/receiving/explicit-targets-a219f250962c/source/original-baseline-manifest.json
new file mode 100644
index 0000000..dd1df26
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/source/original-baseline-manifest.json
@@ -0,0 +1,131 @@
+{
+ "repository": "Jacob-Met/testpilot",
+ "canonical_commit": "e2285d68b2ea5eb2158c0a7e0d936ce5d56f8ff5",
+ "canonical_tree": "22da6a890c2befaf72d3826103538491b07793df",
+ "form": "Scoped materialization of exact current native source plus selected existing regression tests; not a complete canonical checkout",
+ "files": [
+ {
+ "path": "testpilot/__main__.py",
+ "git_blob": "6f149f5484cf7369f4f9569170cd450279c40994",
+ "size": 5275,
+ "sha256": "83caf8df097902120a0cfbd6adb897d5b6be7ba2407b0217606e7038599b5a5f"
+ },
+ {
+ "path": "testpilot/diff.py",
+ "git_blob": "f92280a9ad3f9f79713dce7ba7ae28c55414783a",
+ "size": 9396,
+ "sha256": "659021d4a4b462b86eadc25957407b683c733fec60ae33c0703295cb7640a06c"
+ },
+ {
+ "path": "testpilot/loop.py",
+ "git_blob": "3057a9c7c9777a23e73f8b56979670001eb512fd",
+ "size": 24064,
+ "sha256": "990d6df8b65877e6f25e319771ccad189767575587b4a165f3c3a8e2280c4c93"
+ },
+ {
+ "path": "testpilot/__init__.py",
+ "git_blob": "296210dd0d58703de4a4e4c9ad72a0944c9400ca",
+ "size": 390,
+ "sha256": "65c63b80d7da34a83a6049a79c24b8335878cd49cc79951fdefc5f1532ae841f"
+ },
+ {
+ "path": "testpilot/model.py",
+ "git_blob": "6d4abca38cc1c256df00bcac7923e2dc0bd42b16",
+ "size": 10175,
+ "sha256": "f6ae697f1f5d686ffdcb96f5451d1a9600d7d41c7bcf324d7e8c276a107e8673"
+ },
+ {
+ "path": "testpilot/sandbox.py",
+ "git_blob": "dd6b25f1a93bc6f313ae8c8d01ede5ba83440b5b",
+ "size": 11877,
+ "sha256": "2e852248ffc238371cf2b11b5797a88c519330db359b2cb969bc61aa72ef1931"
+ },
+ {
+ "path": "testpilot/html_report.py",
+ "git_blob": "bb30d903542d836f5afe8e116ba894f93d9d3a0c",
+ "size": 20656,
+ "sha256": "183703d05f32552f7f7c39aa958c39bae61d824e3fa9cd6a6dd686f0d4c228a4"
+ },
+ {
+ "path": "testpilot/_pytest_generated.py",
+ "git_blob": "90b82725bc0eb1ff3d7cb517c4c9dedcfd14bdcc",
+ "size": 2943,
+ "sha256": "d65352d7bade965d7b9dc73efe5d4d01844fa8c7474a1bdcdb3b708f6decf768"
+ },
+ {
+ "path": "tests/conftest.py",
+ "git_blob": "2a855d9fdb8debae0b082088580a4c41e499e33c",
+ "size": 144,
+ "sha256": "0cb67ba91674c07e557440292720aa3c3c111f4c41b7db1f4d219a7c68dc6f52"
+ },
+ {
+ "path": "tests/test_cli_diff_transport.py",
+ "git_blob": "25017791488cf8dd3c7f95b9460d73d691ce8a06",
+ "size": 11507,
+ "sha256": "5748cd3aafbd5aa032b98d531a9091d4a91becdce187a514dafffcc271173bab"
+ },
+ {
+ "path": "tests/test_cli_targets.py",
+ "git_blob": "281550909a8cce1067983199c5122e1bc5c0b78e",
+ "size": 10853,
+ "sha256": "f53a8a7ed6ac752e5bfc1e32a92c88d5985e1da2a8296f317df2ea5b11f5c082"
+ },
+ {
+ "path": "tests/test_diff_source_boundary.py",
+ "git_blob": "74f9ca4d4a31b9ca447f362ac3cbf594333d560f",
+ "size": 5521,
+ "sha256": "f8442bb2e26a4cbdfa7b6320627c780a83264675959ddc6339bc88b57a6faec2"
+ },
+ {
+ "path": "tests/test_fenced_code_boundaries.py",
+ "git_blob": "54c798f49b690d6365ec4de9b0ae78f9d99cd960",
+ "size": 2990,
+ "sha256": "edff7f6dec6c754cdbd03efee84b342103bb1c43a8b614333e1ca83e75bfd91c"
+ },
+ {
+ "path": "tests/test_html_report.py",
+ "git_blob": "655c4ce97b5ffbd2fe81f5b602a2ca12388c27b7",
+ "size": 9811,
+ "sha256": "bb2d6b66ecf929ee6340ae6f39875a07e64dd8c5af2053944b16ad93aa7d6f5c"
+ },
+ {
+ "path": "tests/test_loop.py",
+ "git_blob": "8540b31caf7c258e9345e62d11cd4796c5e92ba5",
+ "size": 5465,
+ "sha256": "efcf833870d9fb407d324c48d8421d587089407b4b1db9f7ba24fad7c2ed67dc"
+ },
+ {
+ "path": "tests/test_patch_physical_lines.py",
+ "git_blob": "f5a9ff15106d39c953cda3f783f64362634f7ad1",
+ "size": 3085,
+ "sha256": "8318fb4e20f87e4f0cc25013aab9f18b3a4236537bbae5f2fbb7e56a1b2a825b"
+ },
+ {
+ "path": "tests/test_python_source_encoding.py",
+ "git_blob": "903abd6db569a80e3a5a61d734bf376c0ac4c8dd",
+ "size": 5598,
+ "sha256": "85196e144916098af0fe677f01382eeb48c6f175008c76446756c438ee741b89"
+ },
+ {
+ "path": "tests/test_report_text_encoding.py",
+ "git_blob": "44e7622672361ffad936e462b0996816c55d2ddd",
+ "size": 9330,
+ "sha256": "1dde3e4f3b227918e541e4ffc71099a8f36c235c198169bb1a152302ea635a3e"
+ },
+ {
+ "path": "README.md",
+ "git_blob": "eaf87ecdc04aee56e7de8e5ec28ebaec20d0de14",
+ "size": 11550,
+ "sha256": "a75d56db555f787886056157c9d07d625ab18c5f06299561ae9889f3610efe00"
+ },
+ {
+ "path": "pyproject.toml",
+ "git_blob": "952255ac87e2c7da57457fdbf3554914f17f6d05",
+ "size": 676,
+ "sha256": "5503eab7b6c145d4de241e5be805a4fb8f30828053cbe39dc05a43c2896642eb"
+ }
+ ],
+ "scoped_commit": "a67abc30301dbcb357e866de749502045e5e40fd",
+ "scoped_tree": "dc3e2218810a72c293c22a3507b1a03db14461dc",
+ "bytes": 161306
+}
diff --git a/docs/receiving/explicit-targets-a219f250962c/source/publication-mapping.json b/docs/receiving/explicit-targets-a219f250962c/source/publication-mapping.json
new file mode 100644
index 0000000..86c5003
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/source/publication-mapping.json
@@ -0,0 +1,79 @@
+{
+ "schema": "testpilot.publication.mapping.v1",
+ "repository": "Jacob-Met/testpilot",
+ "canonical_parent": "f3b2135cad31852b599350564fe76dd160a6a522",
+ "canonical_parent_tree": "d0107a6f8b3b8cc00dadebc5f794060f275f3f5b",
+ "canonical_parent_leaves": 1041,
+ "native_source_commit": "00b63c8e03cc515160da1202331224167d12d253",
+ "native_source_tree": "d1ba76e3872a0e843821b799f8accb69aa32416d",
+ "native_receiving_parent": "63174afd8e63a0ea562cf188b7d971522f017a30",
+ "native_receiving_parent_tree": "cea947d9aeb2253466593044889a1bb548f8b7c4",
+ "source_preimages": [
+ {
+ "path": "testpilot/targets.py",
+ "actualParentBlob": null,
+ "received631Blob": null,
+ "match": true
+ },
+ {
+ "path": "testpilot/__main__.py",
+ "actualParentBlob": "2d98e2a85320ac2827ea48fa9d58d30a60354b6a",
+ "received631Blob": "2d98e2a85320ac2827ea48fa9d58d30a60354b6a",
+ "match": true
+ },
+ {
+ "path": "testpilot/loop.py",
+ "actualParentBlob": "3057a9c7c9777a23e73f8b56979670001eb512fd",
+ "received631Blob": "3057a9c7c9777a23e73f8b56979670001eb512fd",
+ "match": true
+ },
+ {
+ "path": "testpilot/html_report.py",
+ "actualParentBlob": "bb30d903542d836f5afe8e116ba894f93d9d3a0c",
+ "received631Blob": "bb30d903542d836f5afe8e116ba894f93d9d3a0c",
+ "match": true
+ },
+ {
+ "path": "README.md",
+ "actualParentBlob": "eaf87ecdc04aee56e7de8e5ec28ebaec20d0de14",
+ "received631Blob": "eaf87ecdc04aee56e7de8e5ec28ebaec20d0de14",
+ "match": true
+ },
+ {
+ "path": "docs/EXPLICIT_TARGETS.md",
+ "actualParentBlob": null,
+ "received631Blob": null,
+ "match": true
+ },
+ {
+ "path": "tests/test_explicit_targets.py",
+ "actualParentBlob": null,
+ "received631Blob": null,
+ "match": true
+ },
+ {
+ "path": "tests/test_explicit_target_cli.py",
+ "actualParentBlob": null,
+ "received631Blob": null,
+ "match": true
+ }
+ ],
+ "preserve_all_other_current_parent_leaves": true,
+ "unchanged_parent_leaf_count": 1037,
+ "inherited_parent_change": {
+ "path": "testpilot/diff.py",
+ "previous_blob": "f92280a9ad3f9f79713dce7ba7ae28c55414783a",
+ "current_blob": "29dd325ab288ac1d43fd70f33b207281f5375d4b",
+ "source": "https://github.com/Jacob-Met/testpilot/pull/32",
+ "previous_condition": "if parts and parts[0] in (\"src\", \"lib\"):",
+ "current_condition": "if parts and parts[0] == \"src\":",
+ "whole_file_equal_after_that_single_condition_replacement": true,
+ "qualification": "Owner source preserved as current parent; explicit resolver continues to reuse native functions_touching/module_name. Native target receiving remains pinned to631; complete published-tree CI is separate."
+ },
+ "independent": {
+ "packet_manifest_sha256": "17d33526a6b44439e691f9b5cc503d172c15be86a7d83e90867a1b209c8ff6b2",
+ "review_sha256": "7ce638499b5ede479e7b4aa59abffaaa94ae2c1bcc9b1405a06476c6d2a16ef8",
+ "result_sha256": "b47fab55bcc44de2179cfd10c91238b5d1557180d608e21ec6fb3768e4febdd7",
+ "disposition": "ACCEPT exact native00b source and receiving;6groups/23realcommands/3model calls incl repair; no live-model quality claim"
+ }
+}
diff --git a/docs/receiving/explicit-targets-a219f250962c/source/receiving-manifest.json b/docs/receiving/explicit-targets-a219f250962c/source/receiving-manifest.json
new file mode 100644
index 0000000..9aca3d4
--- /dev/null
+++ b/docs/receiving/explicit-targets-a219f250962c/source/receiving-manifest.json
@@ -0,0 +1,95 @@
+{
+ "repository": "Jacob-Met/testpilot",
+ "canonical_base": "63174afd8e63a0ea562cf188b7d971522f017a30",
+ "canonical_base_tree": "cea947d9aeb2253466593044889a1bb548f8b7c4",
+ "source_commit": "00b63c8e03cc515160da1202331224167d12d253",
+ "source_tree": "d1ba76e3872a0e843821b799f8accb69aa32416d",
+ "scoped_base": "33a10cd5b756b8ade8768bd6f8da75f55da369c3",
+ "scope": "Exactly eight source/test/doc paths; source-history bundle is scoped custody, not canonical merge ancestry",
+ "files": [
+ {
+ "path": "README.md",
+ "before": {
+ "git_blob": "eaf87ecdc04aee56e7de8e5ec28ebaec20d0de14",
+ "sha256": "a75d56db555f787886056157c9d07d625ab18c5f06299561ae9889f3610efe00"
+ },
+ "after": {
+ "git_blob": "3848e40614d7328eb5544a28f52cd6a75de38aa1",
+ "sha256": "9c646cbd14a07a5af79afd9a306202d402e2037db7e6557525d9e18387e27f5a",
+ "bytes": 12053
+ }
+ },
+ {
+ "path": "docs/EXPLICIT_TARGETS.md",
+ "before": null,
+ "after": {
+ "git_blob": "3cfe7a0370d48a1e86c6f7c49bb7508fbdfb8abd",
+ "sha256": "dabf34adc34d931749f04debe601fb8f2c6a0ab668278c8539926fb05c88cca8",
+ "bytes": 5497
+ }
+ },
+ {
+ "path": "testpilot/__main__.py",
+ "before": {
+ "git_blob": "2d98e2a85320ac2827ea48fa9d58d30a60354b6a",
+ "sha256": "90577ea3a939642f5cb29818f6360df96d9fa0d35d05af11bf97054425c32c84"
+ },
+ "after": {
+ "git_blob": "5686cb41d555890b906d2ea322149f0673e8049d",
+ "sha256": "319bc6e8b06615d657530eb35fa2f7f3e6a883a36c98ea1540fdf4c2815eac55",
+ "bytes": 7479
+ }
+ },
+ {
+ "path": "testpilot/html_report.py",
+ "before": {
+ "git_blob": "bb30d903542d836f5afe8e116ba894f93d9d3a0c",
+ "sha256": "183703d05f32552f7f7c39aa958c39bae61d824e3fa9cd6a6dd686f0d4c228a4"
+ },
+ "after": {
+ "git_blob": "2fcc6441473684c2f8bdd16d314e8b258de51edf",
+ "sha256": "f2b65ee9201f9e91ad183fcb8f6b8bf6e69c77b1a066fcd90bb431951e41a2f6",
+ "bytes": 20982
+ }
+ },
+ {
+ "path": "testpilot/loop.py",
+ "before": {
+ "git_blob": "3057a9c7c9777a23e73f8b56979670001eb512fd",
+ "sha256": "990d6df8b65877e6f25e319771ccad189767575587b4a165f3c3a8e2280c4c93"
+ },
+ "after": {
+ "git_blob": "8a567fb35f29dc242413df3678f5f63fe08caa4f",
+ "sha256": "3ea786ae5466ebdd6087b03d829580c333882e6fd429aaf54099320b4aa0bb08",
+ "bytes": 25406
+ }
+ },
+ {
+ "path": "testpilot/targets.py",
+ "before": null,
+ "after": {
+ "git_blob": "2da1600eca93edbe47605e6ca2a9e715663fa370",
+ "sha256": "35add23585b0e88246288572f49bd857ebf4af83a221b9129e784e9b456c1ab0",
+ "bytes": 5410
+ }
+ },
+ {
+ "path": "tests/test_explicit_target_cli.py",
+ "before": null,
+ "after": {
+ "git_blob": "d01e0dad0cb73fe1f54b6c88d8287792dd587869",
+ "sha256": "01eb0a6782448a59bbf876fa1d8cbf8398bb05b461f5875b4d125b0147ab3365",
+ "bytes": 1930
+ }
+ },
+ {
+ "path": "tests/test_explicit_targets.py",
+ "before": null,
+ "after": {
+ "git_blob": "438d1c23335d79d4151a198f7e3606595cd68785",
+ "sha256": "bd40f190a52195eed73145cbd0a721b0c555307dfd358ec1d795df3799026b1d",
+ "bytes": 5268
+ }
+ }
+ ]
+}
diff --git a/docs/receiving/explicit-targets-a219f250962c/source/source-history.bundle b/docs/receiving/explicit-targets-a219f250962c/source/source-history.bundle
new file mode 100644
index 0000000000000000000000000000000000000000..2d2480b13bbec4219750f43454dfde22694f7210
GIT binary patch
literal 76638
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%0AqRY>72YzI$Dd4a=>TRG#3zjksr{BVJBbgPDE;W
zcH%SoM3e@Gjo~ycw;i$LQ+<|%;QlMR=SthW=yP1hNUix9T@BnJ`9{SmN0$
z87v$xgNBb(g8F~-C{}UMBFtI8EWKpEtefAORc5Az1C-AEQr^AZi(OVK28+Vt(o%Uxg&=!em|(mUc1%9v3XwTTm*NYlC=DJp?)F9YZh79`?y}r
zFTmy6;95+{Yk<4LH<#DykGG5U3vc5`<@i(l@|)Iw&eK;Gv#j_V%Ndpm*ANYuFstXB
zK>7^BnMZ~6q>moh-iNFlY(a5;X;E@&d{TaCUP@w7B}iY%&r`l$hC5HW)SGD?KK*ry
zv27SXvcA-cf}G6c%o3`ln>;V!=~pud9qp$%NE%*sE}z$M<$7m^fYy8U{iZJk1S7eQ8HW(VFrM>_vqa
zr+hHDJv;y9?B!+j`sQZhjg{PfO%;NHL7FHE!jg$RePDhN7PMrUG+%?kAWo?e-fOzK
z0H01unhBg~0z8CYON^K2RIp$)@ZgiMg(sMNTlj*d3v&}ccp0J