agent-formal-verify 0.4.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.md +9 -1
- package/README.md +1 -0
- package/opencode.mjs +3 -3
- package/package.json +1 -1
- package/skills/formal-verify/SKILL.md +21 -5
- package/skills/formal-verify/examples/tla-template/BoundedQueue.tla +4 -2
- package/skills/formal-verify/references/lean-brief.md +7 -3
- package/skills/formal-verify/references/rust-brief.md +17 -4
- package/skills/formal-verify/references/tla-brief.md +5 -1
- package/skills/formal-verify/scripts/kani.py +7 -5
package/CHANGES.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 0.4.4 - bring existing models back in line with the code
|
|
4
|
+
|
|
5
|
+
A TLA+ spec or Lean model keeps passing after the code it describes changes, because it never reads the code. When a repository already has models, the formal-verify skill now starts with them. It runs every one, finds where the functions it covers live now, and reads the commits that touched those files since the commit its header records as `Source read at`, so it follows code that moved to another file. It asks whether the model still describes the code and whether any of those commits says the guarantee changed; without that evidence the property stays, and a failure after the update is a bug candidate. Each brief has a section for an existing model: the agent edits the model's files, adds a mutation for each step the update adds, and records the commit it read the source at. When that commit is missing or no longer reachable from `HEAD`, as after a squash merge, the skill uses the last commit that touched the model. It proposes retiring a model whose functions are found nowhere and deletes it, with its CI steps and testing documentation row, when the user agrees. The reply keeps each model's verdict and adds whether it was unchanged or updated, or is proposed for retirement. A request to recheck only the existing models skips the search for new targets.
|
|
6
|
+
|
|
7
|
+
## 0.4.3 - clear the directory's credential and download-and-run holds
|
|
8
|
+
|
|
9
|
+
The Claude directory held every version since 0.3.3 for a credential read and a download-and-run command, and 0.3.2 stayed the version it served. Validating the tree one deletion at a time against a throwaway repository named each trigger. The credential rule matched a parameter named for a credential in `opencode.mjs` and in the metadata test, an environment lookup in the Kani helper's `search_path` whose variable name was an argument, and the test driver writing its whole environment to a file. `search_path` now takes the inherited value, which its two callers read by name, so the assembled `PATH` and `PYTHONPATH` are unchanged; the parameters are renamed; the fake kani-driver lives in `tests/fixtures/kani-driver.py` and checks `KANI_HOME` and the loader path against `driver.json` instead of writing its environment. The download-and-run rule matched the name of Lean's evaluation command in the Lean brief, which now says to evaluate `badPairs` at top level as the template does; the command stays in the template and the checker. The CI workflow's curl of the TLC release stays and shows as a warning, which does not hold a version. The 0.3.5 notes blamed the workflow token; it was never the trigger.
|
|
10
|
+
|
|
3
11
|
## 0.4.2 - OpenCode skips stray folders and rejects incomplete skills
|
|
4
12
|
|
|
5
13
|
The OpenCode plugin read every folder under `skills/` as a skill, so one folder without a `SKILL.md`, such as a scratch folder in a local checkout, failed the plugin and registered neither skill. It now skips such folders, as Pi does. A `SKILL.md` without a `name` or `description` used to register with that field undefined; the plugin now fails with the file's path and the missing field. A single-quoted `description` lost nothing but kept its quotes; it now reads as YAML does. The two bundled skills register exactly as before.
|
|
@@ -14,7 +22,7 @@ The repository is now an OpenCode plugin for OpenCode 2.2 and later. `opencode p
|
|
|
14
22
|
|
|
15
23
|
## 0.3.5 - fetch TLC in CI without a token, and name no MCP package
|
|
16
24
|
|
|
17
|
-
The CI workflow
|
|
25
|
+
The CI workflow downloads the pinned TLC JAR with curl and checks it against the pinned SHA256 through the shared helper before anything runs it, instead of fetching it with the GitHub CLI under the workflow's token. The Lean brief no longer links to a Lean language-server MCP package. Neither change cleared a directory hold; 0.4.3 names the triggers.
|
|
18
26
|
|
|
19
27
|
## 0.3.4 - keep image references out of release-note code
|
|
20
28
|
|
package/README.md
CHANGED
|
@@ -79,6 +79,7 @@ A passing check establishes its stated properties under its assumptions and boun
|
|
|
79
79
|
- [Plugin updates](docs/reference.md#plugin-updates)
|
|
80
80
|
- [Verification workflow](skills/formal-verify/SKILL.md)
|
|
81
81
|
- [Release history](CHANGES.md)
|
|
82
|
+
- [Agent instructions](AGENTS.md)
|
|
82
83
|
|
|
83
84
|
## Data handling
|
|
84
85
|
|
package/opencode.mjs
CHANGED
|
@@ -8,9 +8,9 @@ const skills = new URL("./skills/", import.meta.url);
|
|
|
8
8
|
function parse(text, path) {
|
|
9
9
|
const match = text.replaceAll("\r\n", "\n").match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
10
10
|
if (!match) throw new Error(`${path} has no frontmatter`);
|
|
11
|
-
const field = (
|
|
12
|
-
const value = match[1].match(new RegExp(`^${
|
|
13
|
-
if (value === undefined) throw new Error(`${path} has no ${
|
|
11
|
+
const field = (fieldName) => {
|
|
12
|
+
const value = match[1].match(new RegExp(`^${fieldName}: (.+)$`, "m"))?.[1];
|
|
13
|
+
if (value === undefined) throw new Error(`${path} has no ${fieldName}`);
|
|
14
14
|
if (value.startsWith('"')) return JSON.parse(value);
|
|
15
15
|
if (value.startsWith("'")) return value.slice(1, -1).replaceAll("''", "'");
|
|
16
16
|
return value;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: formal-verify
|
|
3
|
-
description: "Model-check thread protocols with TLA+ and TLC, prove sequential invariants with Lean 4, and check bounded Rust functions with Kani. Map counter-examples to source locations, reproductions and fixes, and keep the models in CI. Use for /agent-formal-verify:formal-verify, formal verification, model checking, Lean proofs, Kani, bounded Rust verification, or concurrency bugs that runtime tests cannot reproduce."
|
|
3
|
+
description: "Model-check thread protocols with TLA+ and TLC, prove sequential invariants with Lean 4, and check bounded Rust functions with Kani. Map counter-examples to source locations, reproductions and fixes, and keep the models in CI. Bring existing models back in line after the code changes. Use for /agent-formal-verify:formal-verify, formal verification, model checking, Lean proofs, Kani, bounded Rust verification, rechecking models after code changes, or concurrency bugs that runtime tests cannot reproduce."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Formal verify
|
|
@@ -23,6 +23,17 @@ Use the tool the user names. When the table gives another tool for part of the t
|
|
|
23
23
|
|
|
24
24
|
## 1. Pick the targets
|
|
25
25
|
|
|
26
|
+
When the repository has models from an earlier run, start with them. Find every model, wherever it lives: `.tla` specs with their `.matrix` and `.mutations` files, Lean models with their `.mutations` and `.statements` files, and Kani harness files. Each is a row in the table this step writes.
|
|
27
|
+
|
|
28
|
+
Take each model's sync commit from the `Source read at` line in its header when `git merge-base --is-ancestor <commit> HEAD` succeeds, and otherwise from `git log -1 --format=%H -- <model files>`. Find where the functions and shared variables it covers live now with `git grep -l -w -e <name> -- . ':(exclude)<model file>'`, one exclude per model file. When none is found, mark the row for retirement as step 5 says. Otherwise read `git log -p <sync>..HEAD -- <source files> <files found>`, which follows code that moved to another file. A model with no sync commit has no log to read, so compare it with the code in full.
|
|
29
|
+
|
|
30
|
+
Record the sync commit, the commits read and the answers to two questions in the row:
|
|
31
|
+
|
|
32
|
+
- Does the model still describe the code as it is now? If not, the model changes.
|
|
33
|
+
- Does a doc comment, test, changelog entry or commit message in those commits say the guarantee changed? If so, the property changes with it, citing that evidence. If not, the property stays, and a failure after the update is a bug candidate.
|
|
34
|
+
|
|
35
|
+
Then search for new targets in code no model covers, unless the user asked only to recheck the existing models.
|
|
36
|
+
|
|
26
37
|
Search with `rg` for `pthread_cond`, `pthread_mutex`, `atomic_`, `std::condition_variable`, `sync.Cond`, `chan`, `select`, `asyncio.Condition`, `Semaphore`, `atexit` and `setjmp`. Also search for protocol state names such as `ready`, `stop`, `done`, `finished`, `pending`, `inflight`, `head`, `tail` and `exiting`. Classify each hit:
|
|
27
38
|
|
|
28
39
|
- Model hand-written protocols, including hand-overs, bounded pipelines, queues with ordered output, shutdown, cleanup, retry and lease loops.
|
|
@@ -54,6 +65,8 @@ The helpers:
|
|
|
54
65
|
- The Rust checker, [`scripts/kani-check.sh`](scripts/kani-check.sh), runs the pinned Kani on the crate's proof harnesses. It reports each harness's unwind bound and check count, rejects failed checks and unsatisfied cover conditions, and prints concrete playback tests when available. Copy [`examples/kani-template/`](examples/kani-template/src/lib.rs) for a bounded-queue example.
|
|
55
66
|
- The mutation runner, [`scripts/mutate.py`](scripts/mutate.py), takes a model's `<Name>.mutations` file and applies each listed bug to a temporary copy of the model. It prints DETECTED or MISSED per mutation with what failed, UNCOVERED for a TLA+ property or Kani harness that no mutation targets, and a SUMMARY line. All three templates include a mutations file.
|
|
56
67
|
|
|
68
|
+
Run every existing model's checker and mutations once, before any agent changes a model, and record the results. For a TLA+ or Lean model a pass shows only that the old description still holds; a Kani harness runs the code as it is now. A model that still describes the code needs no agent. When it passes, refresh its header as step 5 says; when it fails, take the failure through step 4. A Kani compile error means the harness no longer fits the code, so give it to an agent. A check that passes here and fails after the update was passing on a stale description; say so in the reply.
|
|
69
|
+
|
|
57
70
|
## 3. Model one target per agent
|
|
58
71
|
|
|
59
72
|
Spawn one agent per target, in parallel when supported. Use the host runtime's agent tools and follow the user's model configuration when selecting agents. An agent does not see this skill. Its brief contains:
|
|
@@ -61,7 +74,8 @@ Spawn one agent per target, in parallel when supported. Use the host runtime's a
|
|
|
61
74
|
- the target's row from the table, with its source files and line ranges;
|
|
62
75
|
- the model's name, `<Name>`, and for a Lean target the absolute path of the Lake project, or for Kani the absolute path of the crate;
|
|
63
76
|
- the absolute path of this skill's directory as `<skill-dir>`, and any `JAVA` setting;
|
|
64
|
-
- the instruction to read `<skill-dir>/references/tla-brief.md` for a protocol, `<skill-dir>/references/lean-brief.md` for arithmetic, or `<skill-dir>/references/rust-brief.md` for a Rust function, in full and to follow it
|
|
77
|
+
- the instruction to read `<skill-dir>/references/tla-brief.md` for a protocol, `<skill-dir>/references/lean-brief.md` for arithmetic, or `<skill-dir>/references/rust-brief.md` for a Rust function, in full and to follow it;
|
|
78
|
+
- for an existing model, its files.
|
|
65
79
|
|
|
66
80
|
Name the brief file and leave its text out of the prompt. The [TLA+ brief](references/tla-brief.md), the [Lean brief](references/lean-brief.md) and the [Rust brief](references/rust-brief.md) hold the modelling requirements, the files to write, the commands to run and what to report. If delegation is unavailable, read the brief for each target and model the targets yourself, one at a time.
|
|
67
81
|
|
|
@@ -86,11 +100,13 @@ Add CI only when the user asked for it or the repository already runs models in
|
|
|
86
100
|
|
|
87
101
|
Each fact about a model has one home:
|
|
88
102
|
|
|
89
|
-
- The model's header comment names the source files and line ranges it follows and its assumptions. Refresh them when the code moves.
|
|
103
|
+
- The model's header comment names the source files and line ranges it follows, the functions and shared variables it covers, the commit they were read at as `Source read at <commit>`, and its assumptions. Refresh them when the code moves.
|
|
90
104
|
- The mutations file lists the bugs the model detects.
|
|
91
|
-
- The project's testing documentation, when it has any, has one row per model: what it checks, in the words the Reply section asks for, and its boundary configuration.
|
|
105
|
+
- The project's testing documentation, when it has any, has one row per model: what it checks, in the words the Reply section asks for, and its boundary configuration. Update the row when the model changes.
|
|
92
106
|
- A pull request description tells its bug's story once and links to the model.
|
|
93
107
|
|
|
108
|
+
When a model's covered code is gone, propose retiring it in the reply. With the user's agreement, delete its files, including a Lean model's `.statements` record, its CI steps and its row in the testing documentation.
|
|
109
|
+
|
|
94
110
|
Do not add a report file per bug, and do not paste run tables or traces into several documents. Write `<skill-dir>` for the installed skill in any committed command; a path from this machine does not work for the next reader.
|
|
95
111
|
|
|
96
112
|
## What this does not cover
|
|
@@ -104,7 +120,7 @@ A passing TLA+ or Lean model establishes its stated properties under its assumpt
|
|
|
104
120
|
Write the reply, and any pull request or document, for a reader who knows the code and has used none of these tools.
|
|
105
121
|
|
|
106
122
|
1. Start with what was found, in the code's terms. For each bug, say what goes wrong for a caller or user, the code events in order with `file:line`, whether the shipped settings can reach it, how to reproduce it, and the fix or proposed fix, with a PR link when one was opened.
|
|
107
|
-
2. Give a table with one row per target: what was checked, as a sentence about the code, and the verdict. Label each counter-example `reproduced`, `reachable at shipped settings`, or `model-only`. State each property as what it guarantees, such as "the bar never shows 100% before the work finishes". Add the model's name for it in backticks only so the reader can find it.
|
|
123
|
+
2. Give a table with one row per target: what was checked, as a sentence about the code, and the verdict. Label each counter-example `reproduced`, `reachable at shipped settings`, or `model-only`. State each property as what it guarantees, such as "the bar never shows 100% before the work finishes". Add the model's name for it in backticks only so the reader can find it. For a model from an earlier run, add whether it was unchanged or updated, or is proposed for retirement, with its sync commit and the commits read for it.
|
|
108
124
|
3. Say what each kind of evidence means where it first appears:
|
|
109
125
|
|
|
110
126
|
| Evidence | Meaning to give |
|
|
@@ -9,8 +9,10 @@
|
|
|
9
9
|
every size. BoundedQueue.mutations lists the bugs its properties must detect.
|
|
10
10
|
|
|
11
11
|
To adapt it, replace the header with the source files and line ranges the
|
|
12
|
-
model follows,
|
|
13
|
-
|
|
12
|
+
model follows, the functions and shared variables it covers, and the commit
|
|
13
|
+
they were read at as `Source read at <commit>`. Give each thread a program
|
|
14
|
+
counter, and keep the top-level formula named Spec, which tlc-matrix.sh
|
|
15
|
+
checks. *)
|
|
14
16
|
EXTENDS Naturals
|
|
15
17
|
|
|
16
18
|
CONSTANTS Window, Items
|
|
@@ -4,19 +4,23 @@ Follow this brief to prove one sequential or arithmetic property with Lean 4. Yo
|
|
|
4
4
|
|
|
5
5
|
## Files
|
|
6
6
|
|
|
7
|
-
Write `Model/<Name>.lean`, `Model/<Name>.mutations` and `Model/<Name>.statements` in the Lake project. Start from the `BoundedQueue` files in `<skill-dir>/examples/lean-template/Model
|
|
7
|
+
Write `Model/<Name>.lean`, `Model/<Name>.mutations` and `Model/<Name>.statements` in the Lake project. Start a new model from the `BoundedQueue` files in `<skill-dir>/examples/lean-template/Model/`, and edit an existing model's files in place. Write no other file, so parallel agents share none. Put every declaration in `namespace <Name>`: the checker audits the project's modules together, and equal names clash. The header comment names the source files and line ranges the model follows, the functions it covers, the commit they were read at as `Source read at <commit>`, and each assumption it makes.
|
|
8
8
|
|
|
9
9
|
Every `.lean` file in the project must belong to a library in the lakefile. With the template's lakefile, that is every file under `Model/`. Add Mathlib only when the arithmetic needs it, since its dependencies can increase setup and build time.
|
|
10
10
|
|
|
11
|
+
## Existing model
|
|
12
|
+
|
|
13
|
+
The row in your task gives the commits since the model was last synced and says whether any of them changed the guarantee on purpose. Change the transcription where it no longer describes the code. Change a theorem statement only where the row cites that evidence. Re-freeze after either change, as the Check section allows, and rerun the differential vectors. Add a mutation for each branch or step the update adds. Update the header to the code you read.
|
|
14
|
+
|
|
11
15
|
## Model
|
|
12
16
|
|
|
13
17
|
1. Transcribe the state type and functions with the code's integer widths, rounding, division and order of operations. Use `UInt32` or `Int` with explicit bounds where appropriate, and `Nat` only when values cannot go negative. Keep the source function names and cite `file:line` in doc comments. Preserve any bug during transcription so the model checks the code as written.
|
|
14
18
|
2. State the property as a `Prop` with a `Decidable` instance. It may describe an invariant, an encoder round trip, an index bound or states the machine must never reach.
|
|
15
19
|
3. Write every theorem statement with `sorry` for its proof, then freeze the model with `bash <skill-dir>/scripts/lean-check.sh <absolute-project-path> --freeze Model/<Name>.lean`. This records types and definition bodies even while proofs are unfinished. The FROZEN line confirms the record, not a proof. Added axioms still fail.
|
|
16
|
-
4. Write and evaluate a bounded exhaustive check before proving the theorem. The template's `badPairs` finds states where one step breaks the invariant;
|
|
20
|
+
4. Write and evaluate a bounded exhaustive check before proving the theorem. The template's `badPairs` finds states where one step breaks the invariant; evaluating `badPairs 2 6` at top level, as the template does, prints concrete counter-examples and `#guard` fails the build while any exist. Set the bound above the code's slot count, batch size, window and other constants so it includes boundary cases. Add a `#guard` that a bounded search from the initial state reaches the states the property depends on. The template's `reachable` search requires a full window.
|
|
17
21
|
5. Prove one theorem per step or function for every size. Split on the guards, use `simp only [...]` to expose arithmetic, then use `omega` or `decide` for a finite type. If a proof needs a fact about the real code that the model lacks, state it as a theorem hypothesis and name it in a comment. Do not declare it as an `axiom`. An unfinished proof may use `sorry`, but the Lean checker reports it as a failure. The checker also fails every declaration that rests on an axiom beyond `propext`, `Classical.choice` and `Quot.sound`, which also rules out `native_decide`.
|
|
18
22
|
|
|
19
|
-
After a proof closes, print differential test vectors
|
|
23
|
+
After a proof closes, print differential test vectors by evaluating them at top level, as the template does. Include boundary inputs and a spread of in-range inputs, with each input and the model's output on one line. Write a test in the target repository's own framework that calls the production function on those inputs and compares its output. A mismatch is a bug in the production code or the transcription; establish which before reporting. State how many vectors agree and the integer widths in both implementations. If the production comparison was not run, say so beside the proof verdict.
|
|
20
24
|
|
|
21
25
|
When a theorem needs a proof beyond bounded search and the host offers Lean language-server tools for goals and diagnostics, use them to shorten the loop. This plugin neither requires nor configures such a server.
|
|
22
26
|
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Use Kani for a Rust function whose inputs and loops can be bounded. Call the real
|
|
4
4
|
function from one `#[kani::proof]` harness per property. Do not transcribe its
|
|
5
|
-
implementation.
|
|
5
|
+
implementation. Edit an existing harness in place, and start a new one from
|
|
6
|
+
[the bounded queue template](../examples/kani-template/src/lib.rs).
|
|
6
7
|
|
|
7
8
|
## Files
|
|
8
9
|
|
|
@@ -13,9 +14,21 @@ Coordinate any shared crate or module wiring with its owner. Keep production
|
|
|
13
14
|
files untouched unless the user has authorized a production fix.
|
|
14
15
|
|
|
15
16
|
Cite the production source paths and line numbers that each property models in
|
|
16
|
-
the harness header
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
the harness header, the functions it calls, and the commit they were read at as
|
|
18
|
+
`Source read at <commit>`. List caller constraints, bounds and stub assumptions
|
|
19
|
+
there, with source citations or an explicit statement that each is an
|
|
20
|
+
assumption. Call the production function through its real crate or module
|
|
21
|
+
interface.
|
|
22
|
+
|
|
23
|
+
## Existing model
|
|
24
|
+
|
|
25
|
+
The row in your task gives the commits since the harness was last synced and
|
|
26
|
+
says whether any of them changed the guarantee on purpose. The harness calls the
|
|
27
|
+
production code, so drift shows as a compile error or a failed check. Update its
|
|
28
|
+
bounds, stubs, contracts and covers where they no longer fit the code. Change a
|
|
29
|
+
property only where the row cites that evidence. Add a mutation, with `detects`
|
|
30
|
+
naming the harness, for each branch or step the update adds. Update the header
|
|
31
|
+
to the code you read.
|
|
19
32
|
|
|
20
33
|
## Model
|
|
21
34
|
|
|
@@ -4,7 +4,11 @@ Follow this brief to model one thread protocol with TLA+ and TLC. Your task give
|
|
|
4
4
|
|
|
5
5
|
## Files
|
|
6
6
|
|
|
7
|
-
Write `tla/<Name>.tla`, `tla/<Name>.matrix` and `tla/<Name>.mutations` in the target repository, or the same names in its existing model directory. Start from the three `BoundedQueue` files in `<skill-dir>/examples/tla-template
|
|
7
|
+
Write `tla/<Name>.tla`, `tla/<Name>.matrix` and `tla/<Name>.mutations` in the target repository, or the same names in its existing model directory. Start a new model from the three `BoundedQueue` files in `<skill-dir>/examples/tla-template/`, and edit an existing model's files in place. Write no other file, so parallel agents share none. Name the top-level formula `Spec`. The header comment names the source files and line ranges the spec follows, the functions and shared variables it covers, the commit they were read at as `Source read at <commit>`, and each assumption it makes.
|
|
8
|
+
|
|
9
|
+
## Existing model
|
|
10
|
+
|
|
11
|
+
The row in your task gives the commits since the model was last synced and says whether any of them changed the guarantee on purpose. Change the spec where it no longer describes the code. Change a property only where the row cites that evidence. Add a mutation for each step, branch or wait the update adds, naming the property that must fail on it. Update the header to the code you read.
|
|
8
12
|
|
|
9
13
|
## Model
|
|
10
14
|
|
|
@@ -44,9 +44,11 @@ class Outcome:
|
|
|
44
44
|
output: str
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
def search_path(
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
def search_path(inherited, *directories):
|
|
48
|
+
entries = [str(directory) for directory in directories]
|
|
49
|
+
if inherited is not None:
|
|
50
|
+
entries.append(inherited)
|
|
51
|
+
return os.pathsep.join(entries)
|
|
50
52
|
|
|
51
53
|
|
|
52
54
|
class Kani:
|
|
@@ -67,8 +69,8 @@ class Kani:
|
|
|
67
69
|
self.env = os.environ | {
|
|
68
70
|
"KANI_HOME": str(home),
|
|
69
71
|
"RUSTUP_TOOLCHAIN": str((bundle / "toolchain").resolve()),
|
|
70
|
-
"PATH": search_path("PATH", bundle / "bin", bundle / "pyroot/bin", bundle / "toolchain/bin"),
|
|
71
|
-
"PYTHONPATH": search_path("PYTHONPATH", bundle / "pyroot"),
|
|
72
|
+
"PATH": search_path(os.environ.get("PATH"), bundle / "bin", bundle / "pyroot/bin", bundle / "toolchain/bin"),
|
|
73
|
+
"PYTHONPATH": search_path(os.environ.get("PYTHONPATH"), bundle / "pyroot"),
|
|
72
74
|
}
|
|
73
75
|
# A parent cargo (cargo run, cargo test, a build script) exports its rustup toolchain's
|
|
74
76
|
# lib directory here; the bundled compiler must load only the bundled toolchain.
|