Skip to content

Commit ed7a708

Browse files
authored
Merge branch 'main' into copilot/fix-fd1747de-1940-4117-8fb2-d737e187c4de
2 parents 0982c56 + 363e6a5 commit ed7a708

File tree

13 files changed

+334
-41
lines changed

13 files changed

+334
-41
lines changed

.github/copilot-instructions.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# Python Environment Tools (PET) - AI Coding Agent Instructions
2+
3+
## Project Overview
4+
5+
This is a high-performance Rust-based tool for discovering Python environments and virtual environments. It operates as a JSONRPC server consumed by the VS Code Python extension to avoid spawning Python processes repeatedly.
6+
7+
## Architecture
8+
9+
### Core Concepts
10+
11+
- **Locators**: Modular environment discovery components implementing the `Locator` trait (`crates/pet-core/src/lib.rs`)
12+
- **JSONRPC Server**: Main communication interface (`crates/pet/src/jsonrpc.rs`) with stdio/stdout protocol
13+
- **Environment Types**: 15+ supported Python installations (Conda, Poetry, PyEnv, Homebrew, Windows Store, etc.)
14+
- **Reporter Pattern**: Asynchronous environment discovery reporting via the `Reporter` trait
15+
16+
### Key Architecture Files
17+
18+
- `crates/pet/src/locators.rs` - Ordered locator creation and fallback identification logic
19+
- `crates/pet/src/find.rs` - Multi-threaded environment discovery coordination
20+
- `crates/pet-core/src/lib.rs` - Core traits and configuration structures
21+
- `docs/JSONRPC.md` - Complete API specification with TypeScript interfaces
22+
23+
## Development Workflow
24+
25+
### Building & Testing
26+
27+
```bash
28+
# Standard build
29+
cargo build
30+
31+
# Release build (optimized for performance)
32+
cargo build --release
33+
34+
# Run tests with specific CI features
35+
cargo test --features ci
36+
cargo test --features ci-poetry-global
37+
38+
# Run JSONRPC server
39+
./target/debug/pet server
40+
```
41+
42+
### Feature-Gated Testing
43+
44+
Tests use feature flags for different environments:
45+
46+
- `ci` - General CI environment tests
47+
- `ci-jupyter-container` - Jupyter container-specific tests
48+
- `ci-homebrew-container` - Homebrew container tests
49+
- `ci-poetry-*` - Poetry-specific test variants
50+
51+
### Locator Development Pattern
52+
53+
When adding new environment types:
54+
55+
1. **Create new crate**: `crates/pet-{name}/`
56+
2. **Implement Locator trait**: Key methods are `try_from()` (identification) and `find()` (discovery)
57+
3. **Add to locator chain**: Update `create_locators()` in `crates/pet/src/locators.rs` - ORDER MATTERS
58+
4. **Platform-specific**: Use `#[cfg(windows)]`, `#[cfg(unix)]`, `#[cfg(target_os = "macos")]`
59+
60+
Example structure:
61+
62+
```rust
63+
impl Locator for MyLocator {
64+
fn get_kind(&self) -> LocatorKind { LocatorKind::MyType }
65+
fn supported_categories(&self) -> Vec<PythonEnvironmentKind> { vec![PythonEnvironmentKind::MyType] }
66+
fn try_from(&self, env: &PythonEnv) -> Option<PythonEnvironment> { /* identification logic */ }
67+
fn find(&self, reporter: &dyn Reporter) { /* discovery logic */ }
68+
}
69+
```
70+
71+
## Critical Patterns
72+
73+
### Performance Principles (from `crates/pet/README.md`)
74+
75+
1. **Avoid spawning processes** - Extract info from files/filesystem when possible
76+
2. **Report immediately** - Use Reporter pattern for async discovery
77+
3. **Complete information** - Gather all environment details in one pass, not incrementally
78+
79+
### JSONRPC Communication Flow
80+
81+
1. Client sends `configure` request (must be first)
82+
2. Client sends `refresh` request to discover environments
83+
3. Server sends `environment` notifications as discoveries happen
84+
4. Optional: `resolve` request for individual Python executables
85+
86+
### Testing Verification Pattern
87+
88+
Tests validate discovered environments using 4 verification methods:
89+
90+
1. Spawn Python to verify `sys.prefix` and `sys.version`
91+
2. Use `try_from()` with executable to get same info
92+
3. Test symlink identification
93+
4. Use `resolve` method for consistency
94+
95+
## Environment-Specific Notes
96+
97+
### Conda Environments
98+
99+
- Supports detection from history files and conda-meta directories
100+
- Manager detection via spawning conda executable in background threads
101+
- Complex prefix/name relationships for base vs named environments
102+
103+
### Poetry Environments
104+
105+
- Hash-based environment naming: `{project-name}-{hash}-py`
106+
- Project-specific virtual environments in configured cache directories
107+
- Configuration hierarchy: local poetry.toml → global config
108+
109+
### Platform Differences
110+
111+
- **Windows**: Registry + Windows Store detection, different path separators
112+
- **macOS**: Xcode Command Line Tools, python.org, Homebrew paths
113+
- **Linux**: Global system paths (`/usr/bin`, `/usr/local/bin`)
114+
115+
## Common Gotchas
116+
117+
- **Locator order matters** in `create_locators()` - more specific before generic
118+
- **Thread safety** - Heavy use of Arc/Mutex for concurrent discovery
119+
- **Feature flags** - Many tests only run with specific CI features enabled
120+
- **Path canonicalization** - Symlink resolution varies by platform
121+
- **Caching** - Optional cache directory for expensive operations (conda spawning)
122+
123+
## Files to Read First
124+
125+
1. `docs/JSONRPC.md` - Understanding the external API
126+
2. `crates/pet/src/locators.rs` - Core architecture patterns
127+
3. `crates/pet-core/src/lib.rs` - Essential traits and types
128+
4. `crates/pet/tests/ci_test.rs` - Comprehensive testing patterns
129+
130+
131+
## Scripts
132+
- Use `cargo fetch` to download all dependencies
133+
- Use `rustup component add clippy` to install Clippy linter
134+
- Use `cargo fmt --all` to format code in all packages
135+
- Use `cargo build` to build the project
136+
- Use `cargo test --all` to test all packages (this can take a few seconds)
137+
- Use `cargo test [TESTNAME]` to test a specific test
138+
- Use `cargo test -p [SPEC]` to test a specific package
139+
- Use `cargo test --all` to test all packages

.github/prompts/analyze.prompt.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
mode: agent
3+
description: Root cause analysis for a bug in the codebase.
4+
tools: ['codebase', 'editFiles', 'fetch', 'findTestFiles', 'githubRepo', 'search', 'searchResults', 'usages', 'vscodeAPI', 'github', 'get_file_contents', 'get_issue', 'get_issue_comments', 'list_issues', 'list_pull_requests', 'search_code', 'search_issues', 'memory', 'sequentialthinking', 'activePullRequest', 'websearch']
5+
---
6+
You are an expert in this codebase.
7+
8+
Your goal is to analyze a bug or add the new feature, for this you first need to:
9+
* Understand the context of the bug or feature by reading the issue description and comments.
10+
* Ask for clarification from user only if the issue description is not clear.
11+
* Understand the codebase by reading the relevant instruction files and code.
12+
* If its a bug, then identify the root cause of the bug, and explain this to the user.
13+
* If just a number is provided by the user, assume it is an issue number and fetch the issue details.
14+
15+
Based on your above understanding generate a summary of your analysis.
16+
Ensure the plan consists of a Markdown document that has the following sections:
17+
18+
* Overview: A brief description of the bug/feature. If its a bug, then is this bydesign or a bug?
19+
* Root Cause: A detailed explanation of the root cause of the bug, including any relevant code snippets or references to the codebase. (only if it's a bug)
20+
* Requirements: A list of requirements to resolve the bug or add the new feature.
21+
* Additional Considerations: Mention any potential challenges or risks associated with the implementation.
22+
* Proposal: Can and should a solution be implemented? Is it a bug, or is this by design? What are the risks or challenges associated with a solution if it is a feature?
23+
24+
Do not make any code edits, just generate a plan. Use thinking and reasoning skills to outline the steps needed to achieve the desired outcome.
25+
26+
<reminder>
27+
MUST:
28+
- Read instruction file(s) before analyzing code
29+
- Understand codebase, issue and architecture thoroughly
30+
- Perform root cause analysis only if the issue is a bug
31+
- Never make any assumptions, always strive to be thorough and accurate
32+
- Avoid unnecessary repetition and verbosity
33+
- Be concise, but thorough.
34+
35+
MUST NOT:
36+
- Make code changes
37+
- Mention all new or updated lines of code
38+
</reminder>

.github/prompts/explain.prompt.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
mode: agent
3+
description: Analyze the codebase and explain a feature/component in detail.
4+
tools: ['codebase', 'editFiles', 'fetch', 'findTestFiles', 'githubRepo', 'search', 'searchResults', 'usages', 'vscodeAPI', 'search_code', 'memory', 'sequentialthinking', 'websearch']
5+
---
6+
# Code Explanation Guide
7+
You are an expert in this codebase.
8+
Your task is to analyze the user requests and explain the feature/component in detail. Where possible use diagrams to depict the architecture and or flow.
9+
10+
Start by first:
11+
* Understand what needs explaining.
12+
- Read instruction files for the relevant area
13+
- Examine code with appropriate tools
14+
- Understand the codebase by reading the relevant instruction files and code.
15+
- Identify design patterns and architectural decisions
16+
- Use available tools to gather information
17+
- Be thorough before presenting any explanation
18+
19+
Based on your above understanding generate a markdown document that explains the feature/component in detail.
20+
Use thinking and reasoning skills when generating the explanation & ensure the document has the following sections:
21+
22+
* Overview: Brief summary of the feature/component and its purpose.
23+
* Architecture: High-level architecture diagram (if applicable).
24+
* Key Components: List and describe key components involved.
25+
* Data Flow: Explain how data moves through the system.
26+
* Control Flow: Describe the control flow and how components interact.
27+
* Integration Points: Explain how this feature/component integrates with others.
28+
* Additional Considerations: Mention any potential challenges or risks associated with understanding or modifying this feature/component.
29+
Mention any other relevant information that would help in understanding the feature/component.
30+
31+
32+
<reminder>
33+
MUST:
34+
- Do not make any other code edits.
35+
- Read instruction file(s) before analyzing code
36+
- Understand codebase, issue and architecture thoroughly
37+
- Never make any assumptions, always strive to be thorough and accurate
38+
- Avoid unnecessary repetition and verbosity
39+
- Be concise, but thorough.
40+
</reminder>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
mode: agent
3+
description: Executed after a plan has been created to implement a bug fix or feature request.
4+
tools: ['codebase', 'editFiles', 'fetch', 'findTestFiles', 'githubRepo', 'problems', 'runTasks', 'runTests', 'search', 'searchResults', 'terminalLastCommand', 'terminalSelection', 'testFailure', 'usages', 'vscodeAPI', 'github', 'get_file_contents', 'get_issue', 'get_issue_comments', 'list_issues', 'list_pull_requests', 'search_code', 'search_issues', 'memory', 'sequentialthinking', 'activePullRequest', 'copilotCodingAgent', 'websearch']
5+
---
6+
You are an expert in this codebase.
7+
Your task is to now implement the solution.
8+
9+
<reminder>
10+
MUST:
11+
- Adhere to patterns and best practices of the project
12+
- Add required tests to ensure the fix works
13+
</reminder>

.github/prompts/plan.prompt.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
mode: agent
3+
description: Analyze a bug/issue in the codebase and report findings without making code changes.
4+
tools: ['codebase', 'editFiles', 'fetch', 'findTestFiles', 'githubRepo', 'search', 'searchResults', 'usages', 'vscodeAPI', 'github', 'get_file_contents', 'get_issue', 'get_issue_comments', 'list_issues', 'list_pull_requests', 'search_code', 'search_issues', 'memory', 'sequentialthinking', 'activePullRequest', 'websearch']
5+
---
6+
You are an expert in this codebase.
7+
8+
Your goal is to prepare a detailed plan to fix the bug or add the new feature, for this you first need to:
9+
* Understand the context of the bug or feature by reading the issue description and comments.
10+
* Ask for clarification from user only if the issue description is not clear.
11+
* Understand the codebase by reading the relevant instruction files and code.
12+
* If its a bug, then identify the root cause of the bug, and explain this to the user.
13+
* If just a number is provided by the user, assume it is an issue number and fetch the issue details.
14+
15+
Based on your above understanding generate a plan to fix the bug or add the new feature.
16+
Ensure the plan consists of a Markdown document that has the following sections:
17+
18+
* Overview: A brief description of the bug/feature.
19+
* Problem: A detailed explanation of the root cause of the bug, including any relevant code snippets or references to the codebase. (only if it's a bug)
20+
* Solution: A brief summary of the solution including a list of requirements to resolve the bug or add the new feature.
21+
* Additional Considerations: Mention any potential challenges or risks associated with the implementation.
22+
* Implementation Steps: A detailed list of steps to implement the bug fix or new feature.
23+
Note: Limit information to what is necessary for developers and AI assistants to understand the implementation steps.
24+
Note: Adhere to architecture, development and testing patterns in instruction files
25+
26+
Do not make any code edits, just generate a plan. Use thinking and reasoning skills to outline the steps needed to achieve the desired outcome.
27+
28+
<reminder>
29+
MUST:
30+
- Understand codebase, issue and architecture thoroughly
31+
- Adhere to patterns and best practices of the project
32+
- Perform root cause analysis only if the issue is a bug
33+
- Never make any assumptions, always strive to be thorough and accurate
34+
- Avoid unnecessary repetition and verbosity
35+
- Be concise, but thorough.
36+
37+
MUST NOT:
38+
- Make code changes
39+
- Mention all new or updated lines of code
40+
</reminder>

CodeQL.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
path_classifiers:
2+
test:
3+
# Note: use only forward slash / as a path separator.
4+
# * Matches any sequence of characters except a forward slash.
5+
# ** Matches any sequence of characters, including a forward slash.
6+
# This wildcard must either be surrounded by forward slash symbols, or used as the first segment of a path.
7+
# It matches zero or more whole directory segments. There is no need to use a wildcard at the end of a directory path because all sub-directories are automatically matched.
8+
# That is, /anything/ matches the anything directory and all its subdirectories.
9+
# Always enclose the expression in double quotes if it includes *.
10+
- "**/tests/**"
11+
12+
# The default behavior is to tag all files created during the
13+
# build as `generated`. Results are hidden for generated code. You can tag
14+
# further files as being generated by adding them to the `generated` section.
15+
generated:
16+
- target

crates/pet-conda/src/environments.rs

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,14 @@ pub fn get_conda_environment_info(
6464
if conda_dir.exists() {
6565
trace!(
6666
"Conda install folder {}, found, & will be used for the Conda Env: {}",
67-
env_path.display(),
68-
conda_dir.display()
67+
conda_dir.display(),
68+
env_path.display()
6969
);
7070
} else {
7171
warn!(
7272
"Conda install folder {}, does not exist, hence will not be used for the Conda Env: {}",
73-
env_path.display(),
74-
conda_dir.display()
73+
conda_dir.display(),
74+
env_path.display()
7575
);
7676
conda_install_folder = None;
7777
}
@@ -136,6 +136,15 @@ pub fn get_conda_installation_used_to_create_conda_env(env_path: &Path) -> Optio
136136
if let Some(conda_dir) = get_conda_dir_from_cmd(line) {
137137
if is_conda_install(&conda_dir) {
138138
return Some(conda_dir);
139+
} else {
140+
// Possible this is a directory such as `C:\Users\donja\miniconda3\Scripts`
141+
// We try to remove `Scripts` or `bin` from the path in the `get_conda_dir_from_cmd`.
142+
// However if there are other directories such as `condabin` or others we are not aware of, lets try.
143+
if let Some(conda_dir) = conda_dir.parent() {
144+
if is_conda_install(conda_dir) {
145+
return Some(conda_dir.into());
146+
}
147+
}
139148
}
140149
}
141150
}
@@ -233,6 +242,7 @@ fn get_conda_dir_from_cmd(cmd_line: String) -> Option<PathBuf> {
233242
if let Some(conda_dir) = cmd_line.file_name() {
234243
if conda_dir.to_string_lossy().to_lowercase() == "bin"
235244
|| conda_dir.to_string_lossy().to_lowercase() == "scripts"
245+
|| conda_dir.to_string_lossy().to_lowercase() == "condabin"
236246
{
237247
if let Some(conda_dir) = cmd_line.parent() {
238248
// Ensure the casing of the paths are correct.
@@ -285,7 +295,8 @@ fn is_conda_env_name_in_cmd(cmd_line: String, name: &str) -> bool {
285295
// # cmd: /Users/donjayamanne/miniconda3/bin/conda create -n conda1
286296
// # cmd_line: "# cmd: /usr/bin/conda create -p ./prefix-envs/.conda1 python=3.12 -y"
287297
// Look for "-n <name>" in the command line
288-
cmd_line.contains(format!("-n {:?}", name).as_str())
298+
cmd_line.contains(format!("-n {name}").as_str())
299+
|| cmd_line.contains(format!("--name {name}").as_str())
289300
}
290301

291302
pub fn get_activation_command(
@@ -349,4 +360,23 @@ mod tests {
349360
PathBuf::from("/Users/donjayamanne/.pyenv/versions/mambaforge-22.11.1-3")
350361
);
351362
}
363+
364+
#[test]
365+
#[cfg(unix)]
366+
fn verify_conda_env_name() {
367+
let line = "# cmd: /Users/donjayamanne/.pyenv/versions/mambaforge-22.11.1-3/lib/python3.10/site-packages/conda/__main__.py create --yes --name .conda python=3.12";
368+
assert!(is_conda_env_name_in_cmd(line.to_string(), ".conda"));
369+
370+
let mut line = "# cmd: /Users/donjayamanne/.pyenv/versions/mambaforge-22.11.1-3/lib/python3.10/site-packages/conda/__main__.py create --yes -n .conda python=3.12";
371+
assert!(is_conda_env_name_in_cmd(line.to_string(), ".conda"));
372+
373+
line = "# cmd: /Users/donjayamanne/.pyenv/versions/mambaforge-22.11.1-3/lib/python3.10/site-packages/conda/__main__.py create --yes --name .conda python=3.12";
374+
assert!(!is_conda_env_name_in_cmd(line.to_string(), "base"));
375+
376+
line = "# cmd: /Users/donjayamanne/.pyenv/versions/mambaforge-22.11.1-3/lib/python3.10/site-packages/conda/__main__.py create --yes -p .conda python=3.12";
377+
assert!(!is_conda_env_name_in_cmd(line.to_string(), "base"));
378+
379+
line = "# cmd: /Users/donjayamanne/.pyenv/versions/mambaforge-22.11.1-3/lib/python3.10/site-packages/conda/__main__.py create --yes -p .conda python=3.12";
380+
assert!(!is_conda_env_name_in_cmd(line.to_string(), ".conda"));
381+
}
352382
}

crates/pet-conda/src/utils.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ pub fn is_conda_install(path: &Path) -> bool {
3232

3333
/// conda-meta must exist as this contains a mandatory `history` file.
3434
/// The root conda installation folder is also a conda environment (its the base environment).
35+
/// Exclude Pixi environments to avoid misidentifying them as conda environments.
3536
pub fn is_conda_env(path: &Path) -> bool {
36-
path.join("conda-meta").is_dir()
37+
path.join("conda-meta").is_dir() && !path.join("conda-meta").join("pixi").is_file()
3738
}
3839

3940
/// Only used in tests, noop in production.

0 commit comments

Comments
 (0)