path-link 0.2.0__py3-none-any.whl
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.
- path_link-0.2.0.dist-info/METADATA +643 -0
- path_link-0.2.0.dist-info/RECORD +20 -0
- path_link-0.2.0.dist-info/WHEEL +5 -0
- path_link-0.2.0.dist-info/entry_points.txt +2 -0
- path_link-0.2.0.dist-info/licenses/LICENSE +23 -0
- path_link-0.2.0.dist-info/top_level.txt +1 -0
- project_paths/__init__.py +165 -0
- project_paths/builder.py +84 -0
- project_paths/builtin_validators/__init__.py +6 -0
- project_paths/builtin_validators/sandbox.py +159 -0
- project_paths/builtin_validators/strict.py +126 -0
- project_paths/cli.py +201 -0
- project_paths/docs/ai_guidelines.md +668 -0
- project_paths/docs/developer_guide.md +399 -0
- project_paths/docs/metadata.json +119 -0
- project_paths/get_paths.py +104 -0
- project_paths/main.py +2 -0
- project_paths/model.py +76 -0
- project_paths/project_paths_static.py +14 -0
- project_paths/validation.py +94 -0
@@ -0,0 +1,668 @@
|
|
1
|
+
# Assistant Context — ptool-serena v0.2.0
|
2
|
+
|
3
|
+
**Last Updated:** 2025-10-10
|
4
|
+
**Status:** Active (Production)
|
5
|
+
**Canonical Reference:** This document + CLAUDE.md
|
6
|
+
|
7
|
+
---
|
8
|
+
|
9
|
+
## Overview
|
10
|
+
|
11
|
+
`ptool-serena` is a Python library for type-safe project path management using Pydantic models.
|
12
|
+
|
13
|
+
**Key Facts:**
|
14
|
+
- **Package:** `ptool-serena` → imports as `project_paths`
|
15
|
+
- **Pattern:** Dynamic Pydantic model creation via factory methods (v2 API)
|
16
|
+
- **Tool:** `uv` for dependency management
|
17
|
+
- **Python:** 3.11+
|
18
|
+
- **Coverage Target:** ≥90% (enforced in CI)
|
19
|
+
|
20
|
+
**This project uses PTOOL v2 rules:**
|
21
|
+
- All paths managed through validated models
|
22
|
+
- Direct instantiation forbidden (raises `NotImplementedError`)
|
23
|
+
- Factory methods required
|
24
|
+
|
25
|
+
---
|
26
|
+
|
27
|
+
## Quick Start (5 Minutes)
|
28
|
+
|
29
|
+
### 1. Environment Setup
|
30
|
+
|
31
|
+
```bash
|
32
|
+
# Install dependencies (uv handles venv automatically)
|
33
|
+
uv pip install -e ".[test]"
|
34
|
+
|
35
|
+
# One-liner smoke test (verify installation)
|
36
|
+
uv run python -c "from project_paths import ProjectPaths; p=ProjectPaths.from_pyproject(); print('✅ OK:', len(p.to_dict()), 'paths loaded')"
|
37
|
+
```
|
38
|
+
|
39
|
+
### 2. Basic Usage
|
40
|
+
|
41
|
+
```python
|
42
|
+
from project_paths import ProjectPaths
|
43
|
+
|
44
|
+
# ✅ CORRECT: Use factory methods
|
45
|
+
paths = ProjectPaths.from_pyproject() # Load from pyproject.toml
|
46
|
+
# OR
|
47
|
+
paths = ProjectPaths.from_config(".paths") # Load from .paths file
|
48
|
+
|
49
|
+
# Access paths (all are pathlib.Path objects)
|
50
|
+
config_dir = paths.config_dir
|
51
|
+
settings = paths["settings_file"] # Dict-style access
|
52
|
+
|
53
|
+
# ❌ FORBIDDEN: Direct instantiation
|
54
|
+
# paths = ProjectPaths() # Raises NotImplementedError
|
55
|
+
```
|
56
|
+
|
57
|
+
### 3. Run Tests
|
58
|
+
|
59
|
+
```bash
|
60
|
+
uv run pytest # All tests
|
61
|
+
uv run pytest --cov=src --cov-report=term # With coverage
|
62
|
+
uv run pytest tests/test_validators.py # Specific file
|
63
|
+
```
|
64
|
+
|
65
|
+
---
|
66
|
+
|
67
|
+
## Essential Commands
|
68
|
+
|
69
|
+
| Purpose | Command |
|
70
|
+
|---------|---------|
|
71
|
+
| **Install** | `uv pip install -e ".[test]"` |
|
72
|
+
| **Smoke test** | `uv run python -c "from project_paths import ProjectPaths; p=ProjectPaths.from_pyproject(); print('✅ OK:', len(p.to_dict()))"` |
|
73
|
+
| **Run all tests** | `uv run pytest` |
|
74
|
+
| **Coverage check** | `uv run pytest --cov=src --cov-report=term-missing:skip-covered` |
|
75
|
+
| **Type check** | `uv run mypy src/` |
|
76
|
+
| **Lint** | `uv run ruff check .` |
|
77
|
+
| **Format** | `uv run ruff format .` |
|
78
|
+
| **Regenerate static model** | `uv run python -c "from project_paths import write_dataclass_file; write_dataclass_file()"` |
|
79
|
+
| **Verify static sync** | `git diff --exit-code src/project_paths/project_paths_static.py` |
|
80
|
+
| **Access AI guidelines** | `uv run python -c "from project_paths import get_ai_guidelines; print(get_ai_guidelines()[:200])"` |
|
81
|
+
| **Access dev guide** | `uv run python -c "from project_paths import get_developer_guide; print(get_developer_guide()[:200])"` |
|
82
|
+
| **Access metadata** | `uv run python -c "import json; from project_paths import get_metadata; print(json.loads(get_metadata())['version'])"` |
|
83
|
+
| **Run example** | `uv run python examples/minimal_project/src/main.py` |
|
84
|
+
|
85
|
+
---
|
86
|
+
|
87
|
+
## Critical Rules (PTOOL v2)
|
88
|
+
|
89
|
+
### ✅ Do
|
90
|
+
|
91
|
+
- **Use factory methods:**
|
92
|
+
```python
|
93
|
+
paths = ProjectPaths.from_pyproject() # For pyproject.toml
|
94
|
+
paths = ProjectPaths.from_config(".paths") # For .paths files
|
95
|
+
```
|
96
|
+
|
97
|
+
- **Treat all values as `pathlib.Path` objects**
|
98
|
+
|
99
|
+
- **Create directories safely:**
|
100
|
+
```python
|
101
|
+
path.mkdir(parents=True, exist_ok=True)
|
102
|
+
```
|
103
|
+
|
104
|
+
- **Validate paths:**
|
105
|
+
```python
|
106
|
+
from project_paths import validate_or_raise
|
107
|
+
from project_paths.builtin_validators import StrictPathValidator
|
108
|
+
|
109
|
+
validator = StrictPathValidator(
|
110
|
+
required=["config_dir", "data_dir"],
|
111
|
+
must_be_dir=["config_dir"],
|
112
|
+
allow_symlinks=False # Default: blocks symlinks
|
113
|
+
)
|
114
|
+
validate_or_raise(paths, validator)
|
115
|
+
```
|
116
|
+
|
117
|
+
- **Keep `.paths` files simple:** key=value format only (no sections)
|
118
|
+
|
119
|
+
### ❌ Don't
|
120
|
+
|
121
|
+
- **Call `ProjectPaths()` directly** — v2 forbids direct instantiation (raises `NotImplementedError`)
|
122
|
+
- **Use `os.path` or raw `Path(...)` joins** in application code
|
123
|
+
- **Add INI sections** to `.paths` files (use dotenv format)
|
124
|
+
- **Edit `project_paths_static.py` manually** (auto-generated file)
|
125
|
+
- **Modify `PYTHONPATH`** (use editable install instead)
|
126
|
+
|
127
|
+
### 🛡️ Enforcement
|
128
|
+
|
129
|
+
**Compliance is automatically enforced by `tests/test_path_policy.py`**, which forbids:
|
130
|
+
- `os.path` usage in `src/**`
|
131
|
+
- Direct `Path(...)` calls in `src/**`
|
132
|
+
- Direct `ProjectPaths()` calls outside `src/project_paths/**` and `examples/**`
|
133
|
+
|
134
|
+
CI will fail if these patterns are detected.
|
135
|
+
|
136
|
+
---
|
137
|
+
|
138
|
+
## Instantiation Rules (v2)
|
139
|
+
|
140
|
+
**Factory methods only — direct instantiation is disabled.**
|
141
|
+
|
142
|
+
```python
|
143
|
+
# ✅ CORRECT: Load from pyproject.toml
|
144
|
+
paths = ProjectPaths.from_pyproject()
|
145
|
+
|
146
|
+
# ✅ CORRECT: Load from custom .paths file
|
147
|
+
paths = ProjectPaths.from_config(".paths")
|
148
|
+
paths = ProjectPaths.from_config("configs/custom.paths")
|
149
|
+
|
150
|
+
# ❌ FORBIDDEN: Direct instantiation
|
151
|
+
paths = ProjectPaths() # Raises NotImplementedError by design
|
152
|
+
```
|
153
|
+
|
154
|
+
**Why:** V2 enforces factory pattern to ensure config is always loaded from a known source.
|
155
|
+
|
156
|
+
---
|
157
|
+
|
158
|
+
## Static Model Sync
|
159
|
+
|
160
|
+
**When to regenerate:** After any change to `[tool.project_paths]` in `pyproject.toml`
|
161
|
+
|
162
|
+
```bash
|
163
|
+
# 1. Regenerate static model
|
164
|
+
uv run python -c "from project_paths import write_dataclass_file; write_dataclass_file()"
|
165
|
+
|
166
|
+
# 2. Verify no drift (CI check)
|
167
|
+
git diff --exit-code src/project_paths/project_paths_static.py || \
|
168
|
+
(echo "❌ Static model drift detected. Commit the regenerated file." && exit 1)
|
169
|
+
```
|
170
|
+
|
171
|
+
**Why:** Static model (`project_paths_static.py`) provides IDE autocomplete and type hints. Must match dynamic model or `tests/test_static_model_equivalence.py` fails.
|
172
|
+
|
173
|
+
**CI Enforcement:** `just check-regen` verifies static model is in sync.
|
174
|
+
|
175
|
+
---
|
176
|
+
|
177
|
+
## Security: TOCTOU Prevention
|
178
|
+
|
179
|
+
**TOCTOU** (Time-of-check to time-of-use) race conditions can create security vulnerabilities when filesystem state changes between checking a path and using it.
|
180
|
+
|
181
|
+
### Unsafe Pattern (Vulnerable)
|
182
|
+
|
183
|
+
```python
|
184
|
+
# ❌ VULNERABLE — filesystem can change between check and use
|
185
|
+
if not my_dir.exists():
|
186
|
+
my_dir.mkdir() # Race condition: symlink could be placed here
|
187
|
+
|
188
|
+
if not my_file.exists():
|
189
|
+
my_file.write_text("data") # Race condition: file could be created/linked
|
190
|
+
```
|
191
|
+
|
192
|
+
### Safe Pattern (Atomic)
|
193
|
+
|
194
|
+
```python
|
195
|
+
# ✅ SAFE — atomic directory creation
|
196
|
+
try:
|
197
|
+
my_dir.mkdir(exist_ok=False)
|
198
|
+
except FileExistsError:
|
199
|
+
if not my_dir.is_dir(): # Re-verify it's actually a directory
|
200
|
+
raise
|
201
|
+
|
202
|
+
# ✅ SAFE — atomic file creation ('x' mode fails if exists)
|
203
|
+
try:
|
204
|
+
with my_file.open("x") as f:
|
205
|
+
f.write("data")
|
206
|
+
except FileExistsError:
|
207
|
+
print("File already exists")
|
208
|
+
```
|
209
|
+
|
210
|
+
### Validator Protection
|
211
|
+
|
212
|
+
**`StrictPathValidator(allow_symlinks=False)` blocks both live and dangling symlinks by default.**
|
213
|
+
|
214
|
+
```python
|
215
|
+
from project_paths.builtin_validators import StrictPathValidator
|
216
|
+
|
217
|
+
# Blocks symlinks (default behavior)
|
218
|
+
validator = StrictPathValidator(
|
219
|
+
required=["config_dir"],
|
220
|
+
allow_symlinks=False # Default: False
|
221
|
+
)
|
222
|
+
|
223
|
+
# Allow symlinks only if explicitly needed
|
224
|
+
validator_permissive = StrictPathValidator(
|
225
|
+
required=["config_dir"],
|
226
|
+
allow_symlinks=True
|
227
|
+
)
|
228
|
+
```
|
229
|
+
|
230
|
+
**Security features:**
|
231
|
+
- Symlink detection: `path.is_symlink()` check before validation
|
232
|
+
- Dangling link detection: Catches broken symlinks
|
233
|
+
- Re-validation: Paths re-checked at use time, not cached
|
234
|
+
|
235
|
+
---
|
236
|
+
|
237
|
+
## Validation Framework
|
238
|
+
|
239
|
+
### Basic Validation
|
240
|
+
|
241
|
+
```python
|
242
|
+
from project_paths import (
|
243
|
+
ProjectPaths,
|
244
|
+
validate_or_raise,
|
245
|
+
ValidationResult,
|
246
|
+
Finding,
|
247
|
+
Severity
|
248
|
+
)
|
249
|
+
from project_paths.builtin_validators import StrictPathValidator
|
250
|
+
|
251
|
+
# Load paths
|
252
|
+
paths = ProjectPaths.from_pyproject()
|
253
|
+
|
254
|
+
# Configure validator
|
255
|
+
validator = StrictPathValidator(
|
256
|
+
required=["config_dir", "data_dir"], # Must exist
|
257
|
+
must_be_dir=["config_dir"], # Must be directory
|
258
|
+
must_be_file=["settings_file"], # Must be file
|
259
|
+
allow_symlinks=False # Block symlinks (default)
|
260
|
+
)
|
261
|
+
|
262
|
+
# Validate (raises PathValidationError on failure)
|
263
|
+
try:
|
264
|
+
validate_or_raise(paths, validator)
|
265
|
+
print("✅ All paths valid!")
|
266
|
+
except PathValidationError as e:
|
267
|
+
print(f"❌ Validation failed:\n{e}")
|
268
|
+
```
|
269
|
+
|
270
|
+
### Manual Validation (No Exception)
|
271
|
+
|
272
|
+
```python
|
273
|
+
# Get ValidationResult without raising
|
274
|
+
result = validator.validate(paths)
|
275
|
+
|
276
|
+
if result.ok():
|
277
|
+
print("✅ Validation passed")
|
278
|
+
else:
|
279
|
+
# Inspect errors
|
280
|
+
for error in result.errors():
|
281
|
+
print(f"ERROR [{error.code}] {error.field}: {error.message}")
|
282
|
+
|
283
|
+
# Inspect warnings
|
284
|
+
for warning in result.warnings():
|
285
|
+
print(f"WARN [{warning.code}] {warning.field}: {warning.message}")
|
286
|
+
```
|
287
|
+
|
288
|
+
### Custom Validators
|
289
|
+
|
290
|
+
```python
|
291
|
+
from dataclasses import dataclass
|
292
|
+
from project_paths import ValidationResult, Finding, Severity
|
293
|
+
|
294
|
+
@dataclass
|
295
|
+
class MyValidator:
|
296
|
+
"""Custom validator example."""
|
297
|
+
required_file: str
|
298
|
+
|
299
|
+
def validate(self, paths) -> ValidationResult:
|
300
|
+
result = ValidationResult()
|
301
|
+
|
302
|
+
# Check custom condition
|
303
|
+
config_path = paths.to_dict().get("config_dir")
|
304
|
+
if config_path and not (config_path / self.required_file).exists():
|
305
|
+
result.add(Finding(
|
306
|
+
severity=Severity.ERROR,
|
307
|
+
code="CUSTOM_FILE_MISSING",
|
308
|
+
field="config_dir",
|
309
|
+
message=f"Required file '{self.required_file}' not found"
|
310
|
+
))
|
311
|
+
|
312
|
+
return result
|
313
|
+
|
314
|
+
# Use custom validator
|
315
|
+
validator = MyValidator(required_file="app.conf")
|
316
|
+
validate_or_raise(paths, validator)
|
317
|
+
```
|
318
|
+
|
319
|
+
---
|
320
|
+
|
321
|
+
## Accessing Documentation Programmatically
|
322
|
+
|
323
|
+
**New in v0.2.0:** Documentation is bundled in the package and accessible offline.
|
324
|
+
|
325
|
+
The package includes three functions to access documentation programmatically, even in airgapped or offline environments. This is especially useful for AI assistants helping users with the package.
|
326
|
+
|
327
|
+
```python
|
328
|
+
from project_paths import get_ai_guidelines, get_developer_guide, get_metadata
|
329
|
+
import json
|
330
|
+
|
331
|
+
# Get AI assistant guidelines (this file - comprehensive usage patterns)
|
332
|
+
ai_docs = get_ai_guidelines()
|
333
|
+
print(f"AI Guidelines: {len(ai_docs):,} characters")
|
334
|
+
|
335
|
+
# Get developer guide (CLAUDE.md - architecture, development setup)
|
336
|
+
dev_docs = get_developer_guide()
|
337
|
+
print(f"Developer Guide: {len(dev_docs):,} characters")
|
338
|
+
|
339
|
+
# Get machine-readable metadata (assistant_handoff.json)
|
340
|
+
metadata_json = get_metadata()
|
341
|
+
metadata = json.loads(metadata_json)
|
342
|
+
print(f"Version: {metadata['version']}")
|
343
|
+
print(f"Public APIs: {metadata['public_api']}")
|
344
|
+
print(f"CLI Commands: {len(metadata['cli_commands'])} available")
|
345
|
+
```
|
346
|
+
|
347
|
+
**Use Cases:**
|
348
|
+
- **AI Assistants**: Provide context to AI agents helping users
|
349
|
+
- **Offline Environments**: Access docs without internet connection
|
350
|
+
- **Enterprise/Airgapped**: Full documentation in restricted environments
|
351
|
+
- **Automation**: Build tools that need package metadata programmatically
|
352
|
+
|
353
|
+
**Location:** Documentation is stored in `src/project_paths/docs/` and bundled via `[tool.setuptools.package-data]` in `pyproject.toml`.
|
354
|
+
|
355
|
+
---
|
356
|
+
|
357
|
+
## Troubleshooting
|
358
|
+
|
359
|
+
### Import Errors
|
360
|
+
|
361
|
+
```bash
|
362
|
+
# 1. Check Python version (must be 3.11+)
|
363
|
+
uv run python -c "import sys; print(sys.version)"
|
364
|
+
|
365
|
+
# 2. Verify editable install
|
366
|
+
uv pip list | grep ptool-serena # Should show editable install
|
367
|
+
|
368
|
+
# 3. Reinstall if needed
|
369
|
+
uv pip install -e ".[test]"
|
370
|
+
|
371
|
+
# 4. Test import
|
372
|
+
uv run python -c "import project_paths; print('✅ Import OK')"
|
373
|
+
```
|
374
|
+
|
375
|
+
### Test Failures
|
376
|
+
|
377
|
+
```bash
|
378
|
+
# 1. Check if static model is out of sync
|
379
|
+
uv run pytest tests/test_static_model_equivalence.py -v
|
380
|
+
|
381
|
+
# 2. If failing, regenerate static model
|
382
|
+
uv run python -c "from project_paths import write_dataclass_file; write_dataclass_file()"
|
383
|
+
|
384
|
+
# 3. Re-run all tests
|
385
|
+
uv run pytest -v
|
386
|
+
```
|
387
|
+
|
388
|
+
### "NotImplementedError" When Creating ProjectPaths
|
389
|
+
|
390
|
+
**This is expected behavior!** Direct instantiation is forbidden in v2.
|
391
|
+
|
392
|
+
```python
|
393
|
+
# ❌ This will fail
|
394
|
+
paths = ProjectPaths() # NotImplementedError
|
395
|
+
|
396
|
+
# ✅ Use factory methods instead
|
397
|
+
paths = ProjectPaths.from_pyproject()
|
398
|
+
```
|
399
|
+
|
400
|
+
### Static Model Base Path Mismatch
|
401
|
+
|
402
|
+
**Symptom:** `test_static_equals_dynamic_values` fails with base_dir mismatch
|
403
|
+
|
404
|
+
**Cause:** Static model was generated in different environment (e.g., Docker `/app`)
|
405
|
+
|
406
|
+
**Fix:**
|
407
|
+
```bash
|
408
|
+
# Regenerate in current environment
|
409
|
+
uv run python -c "from project_paths import write_dataclass_file; write_dataclass_file()"
|
410
|
+
|
411
|
+
# Verify fix
|
412
|
+
uv run pytest tests/test_static_model_equivalence.py -v
|
413
|
+
```
|
414
|
+
|
415
|
+
### Coverage Too Low
|
416
|
+
|
417
|
+
**Current target:** ≥90%
|
418
|
+
|
419
|
+
```bash
|
420
|
+
# Check current coverage
|
421
|
+
uv run pytest --cov=src --cov-report=term-missing:skip-covered
|
422
|
+
|
423
|
+
# Focus on uncovered lines
|
424
|
+
uv run pytest --cov=src --cov-report=term-missing | grep -A 5 "Missing"
|
425
|
+
|
426
|
+
# Add tests for uncovered code (priority: model.py, builder.py)
|
427
|
+
```
|
428
|
+
|
429
|
+
---
|
430
|
+
|
431
|
+
## Architecture Quick Reference
|
432
|
+
|
433
|
+
**Pattern:** Dynamic Pydantic model creation via factory methods
|
434
|
+
|
435
|
+
```
|
436
|
+
User Code
|
437
|
+
↓
|
438
|
+
ProjectPaths.from_pyproject() ← Factory method
|
439
|
+
↓
|
440
|
+
builder.build_field_definitions() ← Loads config, builds Pydantic fields
|
441
|
+
↓
|
442
|
+
model.create_model() ← Generates dynamic Pydantic class
|
443
|
+
↓
|
444
|
+
Returns: ProjectPathsDynamic ← Instance inheriting _ProjectPathsBase
|
445
|
+
```
|
446
|
+
|
447
|
+
### Core Components
|
448
|
+
|
449
|
+
1. **model.py** - Factory methods (`from_pyproject()`, `from_config()`), `_ProjectPathsBase` class
|
450
|
+
2. **builder.py** - Config parsing (`get_paths_from_pyproject`, `get_paths_from_dot_paths`), field generation
|
451
|
+
3. **validation.py** - `ValidationResult`, `Finding`, `Severity`, `validate_or_raise()`
|
452
|
+
4. **builtin_validators/strict.py** - `StrictPathValidator` implementation
|
453
|
+
5. **builtin_validators/sandbox.py** - `SandboxPathValidator` implementation
|
454
|
+
6. **get_paths.py** - Static model generator (`write_dataclass_file()`)
|
455
|
+
7. **__init__.py** - Public API exports, documentation access functions (`get_ai_guidelines`, `get_developer_guide`, `get_metadata`)
|
456
|
+
8. **docs/** - Bundled documentation (ai_guidelines.md, developer_guide.md, metadata.json)
|
457
|
+
|
458
|
+
**See CLAUDE.md for detailed architecture explanation.**
|
459
|
+
|
460
|
+
---
|
461
|
+
|
462
|
+
## Assistant Behavior Guidelines
|
463
|
+
|
464
|
+
When working with this codebase, AI assistants MUST:
|
465
|
+
|
466
|
+
1. **Always use factory methods** — Never call `ProjectPaths()` directly
|
467
|
+
2. **Keep static model in sync** — Regenerate after `pyproject.toml` changes
|
468
|
+
3. **Follow `src` layout** — Use absolute imports: `from project_paths.model import ...`
|
469
|
+
4. **Refer to this document first** — Before any path-related task
|
470
|
+
5. **Verify changes** — Run tests after modifications: `uv run pytest`
|
471
|
+
6. **Use atomic operations** — Follow TOCTOU prevention patterns
|
472
|
+
7. **Validate before writing** — Use `validate_or_raise()` before filesystem operations
|
473
|
+
|
474
|
+
### Example Pattern
|
475
|
+
|
476
|
+
```python
|
477
|
+
from project_paths import ProjectPaths, validate_or_raise
|
478
|
+
from project_paths.builtin_validators import StrictPathValidator
|
479
|
+
|
480
|
+
# 1. Load paths
|
481
|
+
paths = ProjectPaths.from_pyproject()
|
482
|
+
|
483
|
+
# 2. Validate
|
484
|
+
validator = StrictPathValidator(
|
485
|
+
required=["config_dir", "data_dir"],
|
486
|
+
must_be_dir=["config_dir", "data_dir"],
|
487
|
+
allow_symlinks=False
|
488
|
+
)
|
489
|
+
validate_or_raise(paths, validator)
|
490
|
+
|
491
|
+
# 3. Use paths safely
|
492
|
+
config_dir = paths.config_dir
|
493
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
494
|
+
|
495
|
+
config_file = config_dir / "app.conf"
|
496
|
+
try:
|
497
|
+
with config_file.open("x") as f:
|
498
|
+
f.write("# Configuration\n")
|
499
|
+
except FileExistsError:
|
500
|
+
print("Config already exists")
|
501
|
+
```
|
502
|
+
|
503
|
+
---
|
504
|
+
|
505
|
+
## Non-Negotiables
|
506
|
+
|
507
|
+
- **Do not modify `PYTHONPATH`** — All imports must resolve through editable install
|
508
|
+
- **Always work within activated virtual environment** — Use `uv` for consistency
|
509
|
+
- **Never edit auto-generated files** — `project_paths_static.py` is generated by tooling
|
510
|
+
- **Follow policy enforcement** — `tests/test_path_policy.py` will catch violations
|
511
|
+
- **Maintain test coverage** — Target ≥90%, enforced in CI
|
512
|
+
|
513
|
+
---
|
514
|
+
|
515
|
+
## Configuration Formats
|
516
|
+
|
517
|
+
### pyproject.toml
|
518
|
+
|
519
|
+
```toml
|
520
|
+
[tool.project_paths.paths]
|
521
|
+
config_dir = "config"
|
522
|
+
data_dir = "data"
|
523
|
+
logs_dir = "logs"
|
524
|
+
|
525
|
+
[tool.project_paths.files]
|
526
|
+
settings_file = "config/settings.json"
|
527
|
+
log_file = "logs/app.log"
|
528
|
+
```
|
529
|
+
|
530
|
+
### .paths (dotenv format)
|
531
|
+
|
532
|
+
```bash
|
533
|
+
# Simple key=value format (NO SECTIONS)
|
534
|
+
config_dir=config
|
535
|
+
data_dir=data
|
536
|
+
logs_dir=logs
|
537
|
+
settings_file=config/settings.json
|
538
|
+
log_file=logs/app.log
|
539
|
+
```
|
540
|
+
|
541
|
+
**Important:** `.paths` files use dotenv syntax (key=value) with **no `[section]` headers**.
|
542
|
+
|
543
|
+
### Environment Variable Expansion
|
544
|
+
|
545
|
+
**Both pyproject.toml and .paths files support environment variable and home directory expansion.**
|
546
|
+
|
547
|
+
```python
|
548
|
+
# Expansion happens automatically via os.path.expandvars() and os.path.expanduser()
|
549
|
+
```
|
550
|
+
|
551
|
+
**Supported patterns:**
|
552
|
+
- `${VAR}` - Expands to environment variable (empty string if undefined)
|
553
|
+
- `$VAR` - Alternative syntax
|
554
|
+
- `~` - Expands to user's home directory (`Path.home()`)
|
555
|
+
- `~/path` - Path under home directory
|
556
|
+
|
557
|
+
**Example .paths file:**
|
558
|
+
```bash
|
559
|
+
# Environment variables
|
560
|
+
data_dir=${DATA_ROOT}/files
|
561
|
+
cache_dir=/tmp/${USER}_cache
|
562
|
+
|
563
|
+
# Home directory expansion
|
564
|
+
config_dir=~/.config/myapp
|
565
|
+
|
566
|
+
# Combined
|
567
|
+
user_data=~/projects/${PROJECT_NAME}/data
|
568
|
+
```
|
569
|
+
|
570
|
+
**Example pyproject.toml:**
|
571
|
+
```toml
|
572
|
+
[tool.project_paths.paths]
|
573
|
+
data_dir = "${DATA_ROOT}/app_data"
|
574
|
+
config_dir = "~/.config/myapp"
|
575
|
+
```
|
576
|
+
|
577
|
+
**Behavior:**
|
578
|
+
- Undefined environment variables expand to empty string (standard Python behavior)
|
579
|
+
- `~` always expands to actual user home directory
|
580
|
+
- Expansion occurs at load time in `builder.py:make_path_factory()`
|
581
|
+
- All paths are then resolved relative to `base_dir` unless absolute
|
582
|
+
|
583
|
+
---
|
584
|
+
|
585
|
+
## Documentation Index
|
586
|
+
|
587
|
+
**Primary References:**
|
588
|
+
- **CLAUDE.md** — Comprehensive development guide (architecture, patterns, testing)
|
589
|
+
- **README.md** — User-facing documentation and API reference
|
590
|
+
- **assistant_context.md** — This file (quick start + troubleshooting)
|
591
|
+
|
592
|
+
**Project Status:**
|
593
|
+
- **REFACTOR_PLAN_1.md** — Latest refactoring plan (evidence-based, YAGNI analysis)
|
594
|
+
- **REFACTOR_STATUS.md** — Execution status, metrics, and results
|
595
|
+
- **assistant_handoff.json** — Machine-readable project snapshot
|
596
|
+
|
597
|
+
**Policy & Standards:**
|
598
|
+
- **CODE_QUALITY.json** — Development policy (SOLID, KISS, YAGNI principles)
|
599
|
+
- **CHAIN_OF_THOUGHT_GOLDEN_ALL_IN_ONE.json** — Reasoning framework for changes
|
600
|
+
|
601
|
+
---
|
602
|
+
|
603
|
+
## Diagnostic Checklist
|
604
|
+
|
605
|
+
If you encounter issues, run these commands in order:
|
606
|
+
|
607
|
+
```bash
|
608
|
+
# 1. Check Python version
|
609
|
+
uv run python -c "import sys; print('Python:', sys.version)" # Should be 3.11+
|
610
|
+
|
611
|
+
# 2. Check editable install
|
612
|
+
uv pip list | grep ptool-serena # Should show path with "(editable)"
|
613
|
+
|
614
|
+
# 3. Test import
|
615
|
+
uv run python -c "import project_paths; print('✅ Import OK')"
|
616
|
+
|
617
|
+
# 4. Run smoke test
|
618
|
+
uv run python -c "from project_paths import ProjectPaths; p=ProjectPaths.from_pyproject(); print('✅ OK:', len(p.to_dict()), 'paths loaded')"
|
619
|
+
|
620
|
+
# 5. Check config files
|
621
|
+
ls -l pyproject.toml .paths 2>/dev/null
|
622
|
+
|
623
|
+
# 6. Run full test suite
|
624
|
+
uv run pytest -v
|
625
|
+
|
626
|
+
# 7. Check coverage
|
627
|
+
uv run pytest --cov=src --cov-report=term
|
628
|
+
|
629
|
+
# 8. Verify static model sync
|
630
|
+
git diff src/project_paths/project_paths_static.py
|
631
|
+
```
|
632
|
+
|
633
|
+
---
|
634
|
+
|
635
|
+
## Known Issues & Recent Fixes
|
636
|
+
|
637
|
+
### ✅ Recently Resolved (2025-10-10)
|
638
|
+
|
639
|
+
1. **main.py policy violation** — Fixed direct `ProjectPaths()` call → `ProjectPaths.from_pyproject()`
|
640
|
+
2. **Static model base_dir hardcoding** — Fixed to use `Path.cwd` for portability (was hardcoded `/app`)
|
641
|
+
3. **Pytest collection warning** — Renamed `TestCoverageAnalyzer` → `CoverageAnalyzer`
|
642
|
+
|
643
|
+
### 🎯 Active Work
|
644
|
+
|
645
|
+
1. **Coverage improvement** — Current state meets project standards; target ≥90% enforced in CI
|
646
|
+
2. **Documentation consolidation** — Ongoing effort to reduce duplication between docs
|
647
|
+
|
648
|
+
---
|
649
|
+
|
650
|
+
## Summary
|
651
|
+
|
652
|
+
**ptool-serena (v0.2.0)** provides type-safe, validated path management for Python projects.
|
653
|
+
|
654
|
+
**Core Principles:**
|
655
|
+
- Factory methods only (v2 API)
|
656
|
+
- Protocol-based validation
|
657
|
+
- Atomic filesystem operations
|
658
|
+
- Test-enforced policy compliance
|
659
|
+
|
660
|
+
**If code touches the filesystem, it must use PTOOL.**
|
661
|
+
|
662
|
+
---
|
663
|
+
|
664
|
+
**Project:** ptool-serena v0.2.0
|
665
|
+
**Python:** 3.11+
|
666
|
+
**License:** MIT
|
667
|
+
**Package Manager:** uv
|
668
|
+
**Last Verified:** 2025-10-10 (all commands tested against current codebase)
|