Unify documentation rules across languages and fix the Python template
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
19e9b8f2fa
commit
5c0f2f758f
+55
-44
@@ -1,12 +1,25 @@
|
|||||||
# AI Agents - Project Rules
|
# AI Agents - Project Rules
|
||||||
|
|
||||||
**Document Version:** v4 (independent, incremented on structural changes)
|
**Document Version:** v5 (independent, incremented on structural changes)
|
||||||
|
|
||||||
Rules and instructions for AI assistants (Claude Code, Cursor, Copilot, etc.)
|
Language-agnostic rules for AI assistants (Claude Code, Cursor, Copilot, etc.).
|
||||||
|
|
||||||
|
Everything language-specific — package manager, formatter, linter, test framework,
|
||||||
|
logging library, line length, project layout — lives in the matching design document:
|
||||||
|
|
||||||
|
| Project type | Design document |
|
||||||
|
|--------------------|------------------------------|
|
||||||
|
| Python application | `DESIGN_DOCUMENT.md` |
|
||||||
|
| Python library | `DESIGN_DOCUMENT_MODULE.md` |
|
||||||
|
| Rust application | `DESIGN_DOCUMENT.md` |
|
||||||
|
| Rust library | `DESIGN_DOCUMENT_LIB.md` |
|
||||||
|
| Godot | `DESIGN_DOCUMENT_GODOT.md` |
|
||||||
|
|
||||||
|
Where this file and a design document disagree, **the design document wins**.
|
||||||
|
|
||||||
## First-time setup
|
## First-time setup
|
||||||
|
|
||||||
- **On first read of this file, immediately read all other `.md` files in the project root** (e.g. `PROJECT.md`, `CHANGELOG.md`, `DESIGN_DOCUMENT.md`) to get full project context before starting any task.
|
- **On first read of this file, immediately read all other `.md` files in the project root** (e.g. `PROJECT.md`, `CHANGELOG.md`, `DESIGN_DOCUMENT*.md`) to get full project context before starting any task.
|
||||||
|
|
||||||
## Language
|
## Language
|
||||||
|
|
||||||
@@ -14,70 +27,68 @@ Rules and instructions for AI assistants (Claude Code, Cursor, Copilot, etc.)
|
|||||||
|
|
||||||
## Dependency Management
|
## Dependency Management
|
||||||
|
|
||||||
- **Always use `poetry add`** to add dependencies, **never edit `pyproject.toml` directly**
|
- **Always add and remove dependencies through the package manager CLI** — **never edit the manifest** (`pyproject.toml`, `Cargo.toml`, …) by hand
|
||||||
```bash
|
- The exact commands are in the design document for the given language
|
||||||
poetry add requests
|
|
||||||
poetry add --group dev pytest
|
|
||||||
```
|
|
||||||
- Use `poetry remove` to remove dependencies — **never edit `pyproject.toml` manually**
|
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
- Entry points are in the project root (named after project or by purpose: `project_name.py`, `cli.py`, `gui.py`, `server.py`)
|
- Source code, tests and detailed documentation each have their own directory — the concrete layout is in the design document
|
||||||
- A project can have multiple entry points
|
- Detailed documentation belongs in `docs/`, never in the project root
|
||||||
- All modules belong in the `src/` folder
|
- The project root holds only the core documents: `README.md`, `AGENTS.md`, `DESIGN_DOCUMENT*.md`, `PROJECT.md`, `CHANGELOG.md`
|
||||||
- Tests belong in the `tests/` folder
|
- Entry points follow the language convention; a project may have several
|
||||||
- Virtual environment is in `.venv/` (do not copy, do not generate)
|
- The dependency/build directory (`.venv/`, `target/`, …) is tool-managed — do not copy it, do not generate it by hand
|
||||||
|
|
||||||
## Code
|
## Code
|
||||||
|
|
||||||
- Always use type annotations
|
- **Always use static typing** — annotate every parameter and return value
|
||||||
- Follow PEP8 and format with Ruff (88 characters per line)
|
- Format and lint with the tools named in the design document
|
||||||
- Before commit run `poetry run ruff check` and `poetry run mypy`
|
- **Nothing is committed without a clean formatter, linter and test run**
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
- **Use pytest exclusively** - never use the `unittest` module
|
- Use the test framework named in the design document — never a second framework alongside it
|
||||||
- No `unittest.TestCase` classes, no `self.assert*` methods
|
- Arrange-Act-Assert pattern
|
||||||
- Use plain `assert` statements and pytest fixtures
|
- Test naming: `test_<action>_<context>`
|
||||||
|
|
||||||
## Running
|
|
||||||
|
|
||||||
- Use `poetry run` to run scripts:
|
|
||||||
```bash
|
|
||||||
poetry run python project_name.py
|
|
||||||
poetry run pytest
|
|
||||||
```
|
|
||||||
|
|
||||||
## Logging
|
## Logging
|
||||||
|
|
||||||
- Use **loguru** for logging - never use `print()` for debugging
|
- Use the logging library named in the design document — **never `print()` (or its language equivalent) for debugging**
|
||||||
- Never log secrets, passwords, tokens, or API keys
|
- Never log secrets, passwords, tokens, or API keys
|
||||||
|
|
||||||
## Environment and Secrets
|
## Environment and Secrets
|
||||||
|
|
||||||
- Store secrets in `.env` file with `ENV_DEBUG=true/false` variable
|
- Applications store secrets in `.env` and load them at runtime — **never commit `.env`**
|
||||||
- Load secrets using `python-dotenv` and `os.getenv()`
|
- Debug mode is driven by an `ENV_DEBUG=true/false` flag
|
||||||
- **Never commit `.env` file**
|
- Libraries do not read `.env` — configuration is passed in by the caller
|
||||||
|
|
||||||
## Git
|
## Git
|
||||||
|
|
||||||
- `.gitignore` should contain: `.venv/`, `__pycache__/`, `*.pyc`, `.mypy_cache/`, `.env`
|
- `.gitignore` must cover at least: the dependency/build directory, tool caches, and `.env`
|
||||||
- Do not commit `poetry.lock` only if it's a library (for applications, commit it)
|
- **Commit the lock file for applications, do not commit it for libraries**
|
||||||
- **Never commit this documentation** (`DESIGN_DOCUMENT.md`, `AGENTS.md`, `.claudeignore`)
|
- **Never commit shared documentation** (`AGENTS.md`, `DESIGN_DOCUMENT*.md`) — it comes from the documentation repository, not from the project
|
||||||
- `PROJECT.md` **should be committed** - it's project-specific
|
- `README.md`, `PROJECT.md` and `CHANGELOG.md` **are committed** — they are project-specific
|
||||||
|
|
||||||
## Versioning
|
## Versioning
|
||||||
|
|
||||||
- **Always ask user before bumping version** - never increase version automatically
|
|
||||||
- **Keep `CHANGELOG.md` updated** - document all significant changes as they are made
|
|
||||||
- Update `CHANGELOG.md` with changes before version bump
|
|
||||||
- Version is defined in `pyproject.toml` under `[project]` section
|
|
||||||
- Follow semantic versioning (MAJOR.MINOR.PATCH)
|
- Follow semantic versioning (MAJOR.MINOR.PATCH)
|
||||||
|
- **Always ask user before bumping version** — never increase version automatically
|
||||||
|
- **Keep `CHANGELOG.md` updated** — document all significant changes as they are made
|
||||||
|
- Update `CHANGELOG.md` **before** the version bump
|
||||||
|
- The version source of truth is the project manifest (`pyproject.toml`, `Cargo.toml`, `project.godot`)
|
||||||
|
|
||||||
## Task Management
|
## Task Management
|
||||||
|
|
||||||
- **When completing tasks, mark them as done** - if you finish any task with a checkbox anywhere in project documentation, check it off as completed `[ ]` → `[x]`
|
Tasks are **single-line comments in the code**, written with Todo Tree tags:
|
||||||
- **Track all work** - this applies to tasks in `PROJECT.md` (TODO section, Development Roadmap, any checklists) and other documentation
|
|
||||||
- **Update documentation** - when completing changes, update relevant sections in `PROJECT.md`, `CHANGELOG.md`, and architecture diagrams
|
| Tag | Meaning |
|
||||||
- **Keep task lists current** - completed items with `[x]` stay visible to show progress history
|
|----------|--------------------------------------------------|
|
||||||
|
| `TODO:` | work still to be done |
|
||||||
|
| `FIXME:` | something broken that must be repaired |
|
||||||
|
| `BUG:` | a known defect, not fixed yet |
|
||||||
|
| `HACK:` | temporary workaround, needs rewriting |
|
||||||
|
| `NOTE:` | important context for whoever reads this next |
|
||||||
|
|
||||||
|
- **No checkboxes and no numbered task lists in documentation** — the code is the task list
|
||||||
|
- `PROJECT.md` carries only cross-cutting tasks that have no single place in the code
|
||||||
|
- If a tag already exists at a location in code, do not repeat it in `PROJECT.md`
|
||||||
|
- **Update documentation** — when completing changes, update the relevant sections of `PROJECT.md` and `CHANGELOG.md`
|
||||||
|
|||||||
+1
-1
@@ -7,4 +7,4 @@
|
|||||||
- `AGENTS.md`
|
- `AGENTS.md`
|
||||||
- `PROJECT.md`
|
- `PROJECT.md`
|
||||||
- `CHANGELOG.md`
|
- `CHANGELOG.md`
|
||||||
- `DESIGN_DOCUMENT.md`
|
- `DESIGN_DOCUMENT*.md` (`DESIGN_DOCUMENT.md`, `DESIGN_DOCUMENT_MODULE.md`, `DESIGN_DOCUMENT_LIB.md` or `DESIGN_DOCUMENT_GODOT.md`)
|
||||||
@@ -5,4 +5,4 @@ description: Updates changelog in project
|
|||||||
|
|
||||||
Take known info and make changes to CHANGELOG.md to make them up-to-date.
|
Take known info and make changes to CHANGELOG.md to make them up-to-date.
|
||||||
|
|
||||||
Ignore changes to: `docs/`, `CONTEXT.md`, `Documentation_AI.md`, `Documentation_human.md` — documentation and context files are not logged in the changelog.
|
Ignore changes to: `docs/`, `CONTEXT.md`, `AGENTS.md`, `DESIGN_DOCUMENT*.md`, `PROJECT.md` — documentation and context files are not logged in the changelog.
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Godot — append on top of the shared core (Project template/.gitignore).
|
||||||
|
|
||||||
|
# --- Editor and import caches ---
|
||||||
|
.godot/
|
||||||
|
.import/
|
||||||
|
|
||||||
|
# --- Builds ---
|
||||||
|
# Decide per project (DESIGN_DOCUMENT_GODOT.md, section 13): for small internal
|
||||||
|
# distribution the repository may be the channel, otherwise uncomment.
|
||||||
|
# build/
|
||||||
|
|
||||||
|
# NOTE: addons/ IS committed so the project opens cleanly on any machine
|
||||||
|
# (section 11). export_presets.cfg is committed too, but must never
|
||||||
|
# contain keystore passwords or signing credentials.
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
> **Note on Versioning:**
|
> **Note on Versioning:**
|
||||||
> - This document version is independent — reused across Godot projects
|
> - This document version is independent — reused across Godot projects
|
||||||
> - **Project version** source of truth: `project.godot` (`config/version`) mirrored in an autoload `Constants` script
|
> - **Project version** source of truth: `project.godot` (`application/config/version`) mirrored in an autoload `Constants` script
|
||||||
> - Version propagates: `project.godot` → `Constants.gd` → code
|
> - Version propagates: `project.godot` → `Constants.gd` → code
|
||||||
> - `CHANGELOG.md` uses the project version
|
> - `CHANGELOG.md` uses the project version
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
All detailed documentation of features and systems belongs in the `docs/` folder, not in the project root.
|
All detailed documentation of features and systems belongs in the `docs/` folder, not in the project root.
|
||||||
|
|
||||||
The root directory contains only the core documents: `DESIGN_DOCUMENT_GODOT.md`, `AGENTS.md`, `PROJECT.md`, `CHANGELOG.md`.
|
The root directory contains only the core documents: `README.md`, `AGENTS.md`, `DESIGN_DOCUMENT_GODOT.md`, `PROJECT.md`, `CHANGELOG.md`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -213,16 +213,19 @@ When the game is distributed as a standalone build:
|
|||||||
|
|
||||||
### Task notation
|
### Task notation
|
||||||
|
|
||||||
Tasks are written as single-line comments directly in code, or in `PROJECT.md` for cross-cutting concerns:
|
Tasks are written as single-line comments directly in code, using the **Todo Tree** tags defined in `AGENTS.md` (`TODO`, `FIXME`, `BUG`, `HACK`, `NOTE`). `PROJECT.md` carries only cross-cutting tasks that have no single place in the code.
|
||||||
|
|
||||||
```gdscript
|
```gdscript
|
||||||
# TODO: one-liner description of a task to be done
|
# TODO: move this into a reusable StateMachine scene
|
||||||
# FIXME: one-liner description of a known bug to be fixed
|
# FIXME: player clips through the floor at high speed
|
||||||
|
# BUG: hitbox stays active one frame too long
|
||||||
|
# HACK: temporary yield until the animation signal is wired up
|
||||||
|
# NOTE: must run in _physics_process, not _process
|
||||||
```
|
```
|
||||||
|
|
||||||
No other task format is used — no checkboxes, no numbered lists in documentation.
|
No other task format is used — **no checkboxes, no numbered lists in documentation**.
|
||||||
|
|
||||||
If a `# TODO:` comment already exists at a specific location in code, do not repeat it in `PROJECT.md`.
|
If a tag already exists at a specific location in code, do not repeat it in `PROJECT.md`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Shared core .gitignore — language-agnostic.
|
||||||
|
# Append the language-specific file from Python/, Rust/ or Godot/ on top of this.
|
||||||
|
|
||||||
|
# --- Secrets ---
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# --- Logs ---
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# --- IDE / editor ---
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.code-workspace
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
# --- OS ---
|
||||||
|
Thumbs.db
|
||||||
|
Desktop.ini
|
||||||
|
.DS_Store
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Python — append on top of the shared core (Project template/.gitignore).
|
||||||
|
|
||||||
|
# --- Virtual environment ---
|
||||||
|
.venv/
|
||||||
|
|
||||||
|
# --- Bytecode and tool caches ---
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
.mypy_cache/
|
||||||
|
.ruff_cache/
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
|
||||||
|
# --- Build artefacts ---
|
||||||
|
build/
|
||||||
|
*.egg-info/
|
||||||
|
|
||||||
|
# --- Distribution ---
|
||||||
|
# Applications built with PyInstaller COMMIT dist/ — the repository is the
|
||||||
|
# distribution channel (DESIGN_DOCUMENT.md, section 13), so leave the line below
|
||||||
|
# commented out. Libraries publish to PyPI and DO ignore dist/
|
||||||
|
# (DESIGN_DOCUMENT_MODULE.md, section 14) — uncomment it there.
|
||||||
|
# dist/
|
||||||
|
|
||||||
|
# --- Lock file ---
|
||||||
|
# Applications commit poetry.lock — leave the line below commented out.
|
||||||
|
# Libraries do not commit it — uncomment it there.
|
||||||
|
# poetry.lock
|
||||||
+30
-10
@@ -19,13 +19,13 @@
|
|||||||
|
|
||||||
All detailed documentation of features and systems belongs in the `docs/` folder, not in the project root.
|
All detailed documentation of features and systems belongs in the `docs/` folder, not in the project root.
|
||||||
|
|
||||||
The root directory contains only the core documents: `DESIGN_DOCUMENT.md`, `AGENTS.md`, `PROJECT.md`, `CHANGELOG.md`.
|
The root directory contains only the core documents: `README.md`, `AGENTS.md`, `DESIGN_DOCUMENT.md`, `PROJECT.md`, `CHANGELOG.md`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Code Style
|
## 1. Code Style
|
||||||
|
|
||||||
- **PEP8** with 150-character lines (Ruff)
|
- **PEP8** with 120-character lines (Ruff)
|
||||||
- **4 spaces** indentation
|
- **4 spaces** indentation
|
||||||
- **snake_case** functions/variables, **PascalCase** classes, **SCREAMING_SNAKE_CASE** constants
|
- **snake_case** functions/variables, **PascalCase** classes, **SCREAMING_SNAKE_CASE** constants
|
||||||
- **Type hints** required on all functions
|
- **Type hints** required on all functions
|
||||||
@@ -76,7 +76,7 @@ Use **loguru** for all internal logging. Never log secrets, passwords, tokens, o
|
|||||||
|
|
||||||
File sink retains **max 10 log files** (`retention=10`). No rotation by size — each run creates a new file via `{time}` in the filename.
|
File sink retains **max 10 log files** (`retention=10`). No rotation by size — each run creates a new file via `{time}` in the filename.
|
||||||
|
|
||||||
The `DEBUG` sink is only active when `constants.DEBUG` is `True` (controlled by `ENV_DEBUG=true` in `.env`).
|
The `DEBUG` sink is only active when `constants.DEFAULT_DEBUG` is `True` (controlled by `ENV_DEBUG=true` in `.env`).
|
||||||
|
|
||||||
Additional sinks (e.g. GUI log panels) may be added per project as needed.
|
Additional sinks (e.g. GUI log panels) may be added per project as needed.
|
||||||
|
|
||||||
@@ -131,6 +131,7 @@ Run before every commit:
|
|||||||
```bash
|
```bash
|
||||||
poetry run ruff check
|
poetry run ruff check
|
||||||
poetry run mypy
|
poetry run mypy
|
||||||
|
poetry run pytest
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -147,6 +148,10 @@ poetry run <cmd> # Run command in virtualenv
|
|||||||
|
|
||||||
Never edit `pyproject.toml` directly to add or remove dependencies.
|
Never edit `pyproject.toml` directly to add or remove dependencies.
|
||||||
|
|
||||||
|
### poetry.lock
|
||||||
|
|
||||||
|
`poetry.lock` **is committed** for applications — it pins the exact dependency graph and keeps builds reproducible.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 12. Project Structure
|
## 12. Project Structure
|
||||||
@@ -171,12 +176,24 @@ When a project is distributed as a standalone executable (no Python required on
|
|||||||
|
|
||||||
- Use **PyInstaller** to compile each entry point into a single `.exe`
|
- Use **PyInstaller** to compile each entry point into a single `.exe`
|
||||||
- Each tool has its own `.spec` file in the project root
|
- Each tool has its own `.spec` file in the project root
|
||||||
- All console tools must use `console=True` in the `.spec` — tools rely on `input()` and `print()` for user interaction
|
- **Console tools use `console=True`** in the `.spec` — they rely on `input()` and `print()` for user interaction. GUI applications turn the console off with `ENV_BUILD_CONSOLE=false`; that flag is independent of `ENV_DEBUG`
|
||||||
- Compiled executables are stored in `dist/` and **committed to the repository** — the repository serves as the distribution channel for internal teams
|
- Compiled executables are stored in `dist/` and **committed to the repository** — the repository serves as the distribution channel for internal teams
|
||||||
- `.gitignore` must **not** exclude `dist/` in projects that use this deployment model
|
- `.gitignore` must **not** exclude `dist/` in projects that use this deployment model
|
||||||
|
|
||||||
Build command:
|
### prebuild.py
|
||||||
|
|
||||||
|
`prebuild.py` runs before PyInstaller. It verifies that the active interpreter is the project `.venv`, prints the resolved version, and rewrites the `console=` line in the `.spec` to match `.env`.
|
||||||
|
|
||||||
|
| Variable | Default | Effect |
|
||||||
|
|----------|---------|--------|
|
||||||
|
| `ENV_BUILD_CONSOLE` | `true` | Value written into `console=` in the `.spec` |
|
||||||
|
| `ENV_BUILD_SPEC` | `<project folder>.spec` | Which `.spec` file to update |
|
||||||
|
|
||||||
|
`prebuild.py` touches one `.spec` per run — with several entry points, set `ENV_BUILD_SPEC` for each build.
|
||||||
|
|
||||||
|
Build commands:
|
||||||
```bash
|
```bash
|
||||||
|
poetry run python prebuild.py
|
||||||
poetry run pyinstaller ToolName.spec
|
poetry run pyinstaller ToolName.spec
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -200,13 +217,16 @@ poetry run pyinstaller ToolName.spec
|
|||||||
|
|
||||||
### Task notation
|
### Task notation
|
||||||
|
|
||||||
Tasks are written as single-line comments directly in code, or in `PROJECT.md` for cross-cutting concerns:
|
Tasks are written as single-line comments directly in code, using the **Todo Tree** tags defined in `AGENTS.md` (`TODO`, `FIXME`, `BUG`, `HACK`, `NOTE`). `PROJECT.md` carries only cross-cutting tasks that have no single place in the code.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# TODO: one-liner description of a task to be done
|
# TODO: extract this into a separate loader
|
||||||
# FIXME: one-liner description of a known bug to be fixed
|
# FIXME: crashes on an empty file
|
||||||
|
# BUG: rounding is off by one cent on negative amounts
|
||||||
|
# HACK: temporary workaround until the API adds paging
|
||||||
|
# NOTE: order matters here, the parser is stateful
|
||||||
```
|
```
|
||||||
|
|
||||||
No other task format is used — no checkboxes, no numbered lists in documentation.
|
No other task format is used — **no checkboxes, no numbered lists in documentation**.
|
||||||
|
|
||||||
If a `# TODO:` comment already exists at a specific location in code, do not repeat it in `PROJECT.md`.
|
If a tag already exists at a specific location in code, do not repeat it in `PROJECT.md`.
|
||||||
|
|||||||
@@ -18,13 +18,13 @@
|
|||||||
|
|
||||||
All detailed documentation of features and systems belongs in the `docs/` folder, not in the project root.
|
All detailed documentation of features and systems belongs in the `docs/` folder, not in the project root.
|
||||||
|
|
||||||
The root directory contains only the core documents: `DESIGN_DOCUMENT_MODULE.md`, `AGENTS.md`, `PROJECT.md`, `CHANGELOG.md`.
|
The root directory contains only the core documents: `README.md`, `AGENTS.md`, `DESIGN_DOCUMENT_MODULE.md`, `PROJECT.md`, `CHANGELOG.md`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Code Style
|
## 1. Code Style
|
||||||
|
|
||||||
- **PEP8** with 150-character lines (Ruff)
|
- **PEP8** with 120-character lines (Ruff)
|
||||||
- **4 spaces** indentation
|
- **4 spaces** indentation
|
||||||
- **snake_case** functions/variables, **PascalCase** classes, **SCREAMING_SNAKE_CASE** constants
|
- **snake_case** functions/variables, **PascalCase** classes, **SCREAMING_SNAKE_CASE** constants
|
||||||
- **Type hints** required on all functions
|
- **Type hints** required on all functions
|
||||||
@@ -248,13 +248,16 @@ poetry publish # Publishes to PyPI (requires credentials)
|
|||||||
|
|
||||||
### Task notation
|
### Task notation
|
||||||
|
|
||||||
Tasks are written as single-line comments directly in code, or in `PROJECT.md` for cross-cutting concerns:
|
Tasks are written as single-line comments directly in code, using the **Todo Tree** tags defined in `AGENTS.md` (`TODO`, `FIXME`, `BUG`, `HACK`, `NOTE`). `PROJECT.md` carries only cross-cutting tasks that have no single place in the code.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# TODO: one-liner description of a task to be done
|
# TODO: extract this into a separate loader
|
||||||
# FIXME: one-liner description of a known bug to be fixed
|
# FIXME: crashes on an empty file
|
||||||
|
# BUG: rounding is off by one cent on negative amounts
|
||||||
|
# HACK: temporary workaround until the API adds paging
|
||||||
|
# NOTE: order matters here, the parser is stateful
|
||||||
```
|
```
|
||||||
|
|
||||||
No other task format is used — no checkboxes, no numbered lists in documentation.
|
No other task format is used — **no checkboxes, no numbered lists in documentation**.
|
||||||
|
|
||||||
If a `# TODO:` comment already exists at a specific location in code, do not repeat it in `PROJECT.md`.
|
If a tag already exists at a specific location in code, do not repeat it in `PROJECT.md`.
|
||||||
|
|||||||
+3
-4
@@ -16,8 +16,6 @@ template/
|
|||||||
├── pyproject.toml # Poetry config (ruff, mypy, pytest)
|
├── pyproject.toml # Poetry config (ruff, mypy, pytest)
|
||||||
├── src/
|
├── src/
|
||||||
│ ├── __init__.py
|
│ ├── __init__.py
|
||||||
│ └── core/
|
|
||||||
│ ├── __init__.py
|
|
||||||
│ ├── _version.py # Version fallback for PyInstaller
|
│ ├── _version.py # Version fallback for PyInstaller
|
||||||
│ └── constants.py # Version extraction from toml + DEBUG mode
|
│ └── constants.py # Version extraction from toml + DEBUG mode
|
||||||
└── tests/
|
└── tests/
|
||||||
@@ -28,14 +26,15 @@ template/
|
|||||||
## Key Features
|
## Key Features
|
||||||
|
|
||||||
- **Version extraction** from `pyproject.toml` with `_version.py` fallback for PyInstaller builds
|
- **Version extraction** from `pyproject.toml` with `_version.py` fallback for PyInstaller builds
|
||||||
- **DEBUG mode** via `ENV_DEBUG=true` in `.env` (adds " DEV" suffix to version)
|
- **DEBUG mode** via `ENV_DEBUG=true` in `.env` (adds "DEV" suffix to version: v1.2.3DEV)
|
||||||
- **loguru** for logging (never print)
|
- **loguru** for logging (never print)
|
||||||
- **Poetry** for dependency management
|
- **Poetry** for dependency management
|
||||||
- **pytest** for testing (no unittest)
|
- **pytest** for testing (no unittest)
|
||||||
- **ruff + mypy** for linting and type checking
|
- **ruff + mypy** for linting and type checking (120-character lines)
|
||||||
|
|
||||||
## Rules
|
## Rules
|
||||||
|
|
||||||
- No `.example` suffixes - the folder itself is the separator
|
- No `.example` suffixes - the folder itself is the separator
|
||||||
- Generic/reusable format
|
- Generic/reusable format
|
||||||
- Keep files simple and minimal
|
- Keep files simple and minimal
|
||||||
|
- `.gitignore` is the shared core from `Project template/.gitignore` with `Python/.gitignore` appended
|
||||||
|
|||||||
+8
-3
@@ -1,7 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
from src.constants import VERSION
|
from src.constants import VERSION
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
@@ -33,8 +35,11 @@ else:
|
|||||||
|
|
||||||
print(f"✓ Version: {VERSION}")
|
print(f"✓ Version: {VERSION}")
|
||||||
|
|
||||||
env_debug = os.getenv("ENV_DEBUG", "false").lower() == "true"
|
env_debug = os.getenv("ENV_DEBUG", "false").lower() in ("true", "1", "yes")
|
||||||
console_mode = env_debug
|
|
||||||
|
# console=True is the default — console tools rely on input() and print().
|
||||||
|
# GUI applications turn it off with ENV_BUILD_CONSOLE=false in .env.
|
||||||
|
console_mode = os.getenv("ENV_BUILD_CONSOLE", "true").lower() in ("true", "1", "yes")
|
||||||
default_spec = Path(__file__).parent.name + ".spec"
|
default_spec = Path(__file__).parent.name + ".spec"
|
||||||
spec_filename = os.getenv("ENV_BUILD_SPEC", default_spec)
|
spec_filename = os.getenv("ENV_BUILD_SPEC", default_spec)
|
||||||
|
|
||||||
@@ -42,7 +47,7 @@ print(f"\n{'-' * 50}")
|
|||||||
print("BUILD SETTINGS")
|
print("BUILD SETTINGS")
|
||||||
print(f"{'-' * 50}")
|
print(f"{'-' * 50}")
|
||||||
print(f"ENV_DEBUG: {env_debug}")
|
print(f"ENV_DEBUG: {env_debug}")
|
||||||
print(f"Console mode: {console_mode}")
|
print(f"ENV_BUILD_CONSOLE: {console_mode}")
|
||||||
print(f"Spec file: {spec_filename}")
|
print(f"Spec file: {spec_filename}")
|
||||||
|
|
||||||
spec_path = Path(__file__).parent / spec_filename
|
spec_path = Path(__file__).parent / spec_filename
|
||||||
|
|||||||
+17
-8
@@ -1,6 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
Generic application constants template.
|
Generic application constants template.
|
||||||
|
|
||||||
|
Requires Python 3.11+ (uses the stdlib `tomllib` module).
|
||||||
|
|
||||||
Usage in your project:
|
Usage in your project:
|
||||||
1. Copy this file to src/constants.py
|
1. Copy this file to src/constants.py
|
||||||
2. Fill in APP_NAME and APP_FULL_NAME
|
2. Fill in APP_NAME and APP_FULL_NAME
|
||||||
@@ -14,14 +16,14 @@ Version loading priority:
|
|||||||
Debug mode:
|
Debug mode:
|
||||||
Controlled exclusively via .env: ENV_DEBUG=true
|
Controlled exclusively via .env: ENV_DEBUG=true
|
||||||
Accepted true-values: true, 1, yes (case-insensitive)
|
Accepted true-values: true, 1, yes (case-insensitive)
|
||||||
|
When enabled, VERSION carries a "DEV" suffix with no separator: v1.2.3DEV
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import tomllib
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import tomllib
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
@@ -38,22 +40,28 @@ def _load_version() -> str:
|
|||||||
# 1. pyproject.toml
|
# 1. pyproject.toml
|
||||||
try:
|
try:
|
||||||
with open(_PYPROJECT, "rb") as f:
|
with open(_PYPROJECT, "rb") as f:
|
||||||
version = tomllib.load(f)["project"]["version"]
|
version: str = tomllib.load(f)["project"]["version"]
|
||||||
# Write fallback for frozen/PyInstaller builds
|
except (OSError, KeyError, tomllib.TOMLDecodeError):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# Write the fallback used by frozen/PyInstaller builds, which do not ship
|
||||||
|
# pyproject.toml. A read-only or missing target is not a fatal condition.
|
||||||
|
try:
|
||||||
_VERSION_FILE.write_text(
|
_VERSION_FILE.write_text(
|
||||||
f'"""Auto-generated — do not edit manually."""\n__version__ = "{version}"\n',
|
f'"""Auto-generated — do not edit manually."""\n__version__ = "{version}"\n',
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
return version
|
except OSError:
|
||||||
except (FileNotFoundError, KeyError):
|
|
||||||
pass
|
pass
|
||||||
|
return version
|
||||||
|
|
||||||
# 2. _version.py
|
# 2. _version.py
|
||||||
try:
|
try:
|
||||||
from src._version import __version__ # type: ignore[import]
|
from src._version import __version__
|
||||||
return __version__
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
else:
|
||||||
|
return __version__
|
||||||
|
|
||||||
# 3. last resort
|
# 3. last resort
|
||||||
return "0.0.0"
|
return "0.0.0"
|
||||||
@@ -63,6 +71,7 @@ def _load_version() -> str:
|
|||||||
# Debug mode
|
# Debug mode
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def _load_debug() -> bool:
|
def _load_debug() -> bool:
|
||||||
return os.getenv("ENV_DEBUG", "false").lower() in ("true", "1", "yes")
|
return os.getenv("ENV_DEBUG", "false").lower() in ("true", "1", "yes")
|
||||||
|
|
||||||
|
|||||||
+110
-97
@@ -1,93 +1,125 @@
|
|||||||
"""Tests for constants module."""
|
"""Tests for the constants module template."""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import mock_open, patch
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from src import constants
|
||||||
from src.constants import (
|
from src.constants import (
|
||||||
|
APP_FULL_NAME,
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
APP_TITLE,
|
APP_TITLE,
|
||||||
APP_VERSION,
|
DEFAULT_DEBUG,
|
||||||
ENV_DEBUG,
|
VERSION,
|
||||||
get_debug_mode,
|
|
||||||
get_version,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SEMVER = re.compile(r"^\d+\.\d+\.\d+")
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# get_version()
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_version_returns_string() -> None:
|
def _write_pyproject(tmp_path: Path, version: str = "1.2.3") -> Path:
|
||||||
"""get_version() should return a string."""
|
"""Create a minimal pyproject.toml carrying the given version."""
|
||||||
assert isinstance(get_version(), str)
|
pyproject = tmp_path / "pyproject.toml"
|
||||||
|
pyproject.write_text(f'[project]\nname = "demo"\nversion = "{version}"\n', encoding="utf-8")
|
||||||
|
return pyproject
|
||||||
def test_get_version_semver_format() -> None:
|
|
||||||
"""get_version() should return a semver-like string X.Y.Z."""
|
|
||||||
version = get_version()
|
|
||||||
assert re.match(r"^\d+\.\d+\.\d+", version), f"Not semver: {version!r}"
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_version_fallback_when_toml_missing(tmp_path: Path) -> None:
|
|
||||||
"""get_version() returns '0.0.0-unknown' when pyproject.toml and _version.py are both missing."""
|
|
||||||
missing = tmp_path / "nonexistent.toml"
|
|
||||||
with patch("src.constants._PYPROJECT_PATH", missing):
|
|
||||||
result = get_version()
|
|
||||||
# Either fallback _version.py exists (from previous run) or returns unknown
|
|
||||||
assert isinstance(result, str)
|
|
||||||
assert len(result) > 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_version_unknown_fallback(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
||||||
"""get_version() returns '0.0.0-unknown' when all sources are unavailable."""
|
|
||||||
missing = tmp_path / "nonexistent.toml"
|
|
||||||
monkeypatch.setattr("src.constants._PYPROJECT_PATH", missing)
|
|
||||||
|
|
||||||
# Patch _version import to also fail
|
|
||||||
with patch("src.constants.Path.write_text", side_effect=OSError):
|
|
||||||
with patch.dict("sys.modules", {"src._version": None}):
|
|
||||||
result = get_version()
|
|
||||||
|
|
||||||
assert isinstance(result, str)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# get_debug_mode()
|
# _load_version()
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def test_get_debug_mode_returns_bool() -> None:
|
def test_load_version_reads_pyproject(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
"""get_debug_mode() should always return a bool."""
|
"""_load_version() takes the version from [project] in pyproject.toml."""
|
||||||
assert isinstance(get_debug_mode(), bool)
|
monkeypatch.setattr(constants, "_PYPROJECT", _write_pyproject(tmp_path))
|
||||||
|
monkeypatch.setattr(constants, "_VERSION_FILE", tmp_path / "_version.py")
|
||||||
|
|
||||||
|
assert constants._load_version() == "1.2.3"
|
||||||
|
|
||||||
|
|
||||||
def test_get_debug_mode_true(monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_load_version_writes_fallback_file(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
"""get_debug_mode() returns True when ENV_DEBUG=true."""
|
"""_load_version() regenerates _version.py so frozen builds keep the version."""
|
||||||
monkeypatch.setenv("ENV_DEBUG", "true")
|
version_file = tmp_path / "_version.py"
|
||||||
assert get_debug_mode() is True
|
monkeypatch.setattr(constants, "_PYPROJECT", _write_pyproject(tmp_path))
|
||||||
|
monkeypatch.setattr(constants, "_VERSION_FILE", version_file)
|
||||||
|
|
||||||
|
constants._load_version()
|
||||||
|
|
||||||
|
assert '__version__ = "1.2.3"' in version_file.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
def test_get_debug_mode_true_variants(monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_load_version_survives_unwritable_fallback(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
"""get_debug_mode() accepts '1' and 'yes' as truthy values."""
|
"""An unwritable _version.py location must not break version loading."""
|
||||||
for value in ("1", "yes", "YES", "True", "TRUE"):
|
monkeypatch.setattr(constants, "_PYPROJECT", _write_pyproject(tmp_path))
|
||||||
|
monkeypatch.setattr(constants, "_VERSION_FILE", tmp_path / "missing_dir" / "_version.py")
|
||||||
|
|
||||||
|
assert constants._load_version() == "1.2.3"
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_version_falls_back_to_version_module(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
"""Without pyproject.toml the version comes from src/_version.py."""
|
||||||
|
from src import _version
|
||||||
|
|
||||||
|
monkeypatch.setattr(constants, "_PYPROJECT", tmp_path / "nonexistent.toml")
|
||||||
|
|
||||||
|
assert constants._load_version() == _version.__version__
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_version_missing_version_key(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
"""A pyproject.toml without [project] version falls through to the next source."""
|
||||||
|
pyproject = tmp_path / "pyproject.toml"
|
||||||
|
pyproject.write_text('[project]\nname = "demo"\n', encoding="utf-8")
|
||||||
|
monkeypatch.setattr(constants, "_PYPROJECT", pyproject)
|
||||||
|
monkeypatch.setitem(sys.modules, "src._version", None)
|
||||||
|
|
||||||
|
assert constants._load_version() == "0.0.0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_version_malformed_toml(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
"""A malformed pyproject.toml falls through instead of raising."""
|
||||||
|
pyproject = tmp_path / "pyproject.toml"
|
||||||
|
pyproject.write_text("[project\nversion = ", encoding="utf-8")
|
||||||
|
monkeypatch.setattr(constants, "_PYPROJECT", pyproject)
|
||||||
|
monkeypatch.setitem(sys.modules, "src._version", None)
|
||||||
|
|
||||||
|
assert constants._load_version() == "0.0.0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_version_last_resort(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
"""With no source available at all, _load_version() returns '0.0.0'."""
|
||||||
|
monkeypatch.setattr(constants, "_PYPROJECT", tmp_path / "nonexistent.toml")
|
||||||
|
monkeypatch.setitem(sys.modules, "src._version", None)
|
||||||
|
|
||||||
|
assert constants._load_version() == "0.0.0"
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# _load_debug()
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("value", ["true", "TRUE", "True", "1", "yes", "YES"])
|
||||||
|
def test_load_debug_truthy_values(value: str, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
"""_load_debug() accepts 'true', '1' and 'yes' in any casing."""
|
||||||
monkeypatch.setenv("ENV_DEBUG", value)
|
monkeypatch.setenv("ENV_DEBUG", value)
|
||||||
assert get_debug_mode() is True, f"Expected True for ENV_DEBUG={value!r}"
|
|
||||||
|
assert constants._load_debug() is True
|
||||||
|
|
||||||
|
|
||||||
def test_get_debug_mode_false(monkeypatch: pytest.MonkeyPatch) -> None:
|
@pytest.mark.parametrize("value", ["false", "FALSE", "0", "no", "", "maybe"])
|
||||||
"""get_debug_mode() returns False when ENV_DEBUG=false."""
|
def test_load_debug_falsy_values(value: str, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
monkeypatch.setenv("ENV_DEBUG", "false")
|
"""Anything outside the accepted true-values is False."""
|
||||||
assert get_debug_mode() is False
|
monkeypatch.setenv("ENV_DEBUG", value)
|
||||||
|
|
||||||
|
assert constants._load_debug() is False
|
||||||
|
|
||||||
|
|
||||||
def test_get_debug_mode_false_when_unset(monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_load_debug_defaults_to_false(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
"""get_debug_mode() returns False when ENV_DEBUG is not set."""
|
"""_load_debug() returns False when ENV_DEBUG is not set."""
|
||||||
monkeypatch.delenv("ENV_DEBUG", raising=False)
|
monkeypatch.delenv("ENV_DEBUG", raising=False)
|
||||||
assert get_debug_mode() is False
|
|
||||||
|
assert constants._load_debug() is False
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -95,47 +127,28 @@ def test_get_debug_mode_false_when_unset(monkeypatch: pytest.MonkeyPatch) -> Non
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def test_env_debug_is_bool() -> None:
|
def test_app_name_is_set() -> None:
|
||||||
"""ENV_DEBUG should be a bool."""
|
"""APP_NAME and APP_FULL_NAME must be filled in per project."""
|
||||||
assert isinstance(ENV_DEBUG, bool)
|
assert isinstance(APP_NAME, str) and APP_NAME
|
||||||
|
assert isinstance(APP_FULL_NAME, str) and APP_FULL_NAME
|
||||||
|
|
||||||
|
|
||||||
def test_app_version_is_string() -> None:
|
def test_default_debug_is_bool() -> None:
|
||||||
"""APP_VERSION should be a string."""
|
"""DEFAULT_DEBUG should be a bool."""
|
||||||
assert isinstance(APP_VERSION, str)
|
assert isinstance(DEFAULT_DEBUG, bool)
|
||||||
|
|
||||||
|
|
||||||
def test_app_version_semver_format() -> None:
|
def test_version_is_prefixed_semver() -> None:
|
||||||
"""APP_VERSION should follow semver format X.Y.Z."""
|
"""VERSION is the project version prefixed with 'v'."""
|
||||||
assert re.match(r"^\d+\.\d+\.\d+", APP_VERSION), f"Not semver: {APP_VERSION!r}"
|
assert VERSION.startswith("v")
|
||||||
|
assert SEMVER.match(VERSION.removeprefix("v")), f"Not semver: {VERSION!r}"
|
||||||
|
|
||||||
|
|
||||||
def test_app_name_value() -> None:
|
def test_version_dev_suffix_matches_debug_flag() -> None:
|
||||||
"""APP_NAME should be 'X4 SavEd'."""
|
"""VERSION ends with 'DEV' (no separator) exactly when DEFAULT_DEBUG is True."""
|
||||||
assert APP_NAME == "X4 SavEd"
|
assert VERSION.endswith("DEV") is DEFAULT_DEBUG
|
||||||
|
|
||||||
|
|
||||||
def test_app_title_contains_name_and_version() -> None:
|
def test_app_title_is_full_name_and_version() -> None:
|
||||||
"""APP_TITLE should contain APP_NAME and APP_VERSION."""
|
"""APP_TITLE joins APP_FULL_NAME and VERSION with a single space."""
|
||||||
assert APP_NAME in APP_TITLE
|
assert APP_TITLE == f"{APP_FULL_NAME} {VERSION}"
|
||||||
assert APP_VERSION in APP_TITLE
|
|
||||||
|
|
||||||
|
|
||||||
def test_app_title_dev_suffix_when_debug(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
||||||
"""APP_TITLE ends with '-DEV' when ENV_DEBUG is True."""
|
|
||||||
import importlib
|
|
||||||
import src.constants as consts
|
|
||||||
|
|
||||||
monkeypatch.setenv("ENV_DEBUG", "true")
|
|
||||||
monkeypatch.setattr(consts, "ENV_DEBUG", True)
|
|
||||||
title = f"{consts.APP_NAME} v{consts.APP_VERSION}" + ("-DEV" if True else "")
|
|
||||||
assert title.endswith("-DEV")
|
|
||||||
|
|
||||||
|
|
||||||
def test_app_title_no_dev_suffix_when_not_debug(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
||||||
"""APP_TITLE does not end with '-DEV' when ENV_DEBUG is False."""
|
|
||||||
import src.constants as consts
|
|
||||||
|
|
||||||
monkeypatch.setattr(consts, "ENV_DEBUG", False)
|
|
||||||
title = f"{consts.APP_NAME} v{consts.APP_VERSION}" + ("-DEV" if False else "")
|
|
||||||
assert not title.endswith("-DEV")
|
|
||||||
|
|||||||
@@ -12,50 +12,70 @@ This repository is the single source of truth for how I develop software. AI ass
|
|||||||
Dokumentace/
|
Dokumentace/
|
||||||
├── Claude/ # AI assistant configuration
|
├── Claude/ # AI assistant configuration
|
||||||
│ ├── CLAUDE.md # Claude Code session instructions
|
│ ├── CLAUDE.md # Claude Code session instructions
|
||||||
│ ├── AGENTS.md # Rules for all AI assistants
|
│ ├── AGENTS.md # Language-agnostic rules for all AI assistants
|
||||||
│ └── skills/ # Custom Claude Code skills
|
│ └── skills/ # Custom Claude Code skills
|
||||||
│
|
│
|
||||||
├── Python/ # Python development guidelines
|
├── Python/ # Python development guidelines
|
||||||
│ ├── DESIGN_DOCUMENT.md # Guidelines for Python applications
|
│ ├── DESIGN_DOCUMENT.md # Guidelines for Python applications
|
||||||
│ ├── DESIGN_DOCUMENT_MODULE.md # Guidelines for Python libraries
|
│ ├── DESIGN_DOCUMENT_MODULE.md # Guidelines for Python libraries
|
||||||
│ ├── TEMPLATE.md # New project template specification
|
│ ├── TEMPLATE.md # New project template specification
|
||||||
│ └── prebuild.py # Pre-build script (PyInstaller)
|
│ ├── .gitignore # Python-specific ignore rules
|
||||||
|
│ ├── prebuild.py # Pre-build script (PyInstaller)
|
||||||
|
│ ├── src/ # Reference constants module + version fallback
|
||||||
|
│ └── tests/ # Tests for the reference module
|
||||||
│
|
│
|
||||||
├── Rust/ # Rust development guidelines
|
├── Rust/ # Rust development guidelines
|
||||||
│ ├── DESIGN_DOCUMENT.md # Guidelines for Rust applications
|
│ ├── DESIGN_DOCUMENT.md # Guidelines for Rust applications
|
||||||
│ └── DESIGN_DOCUMENT_LIB.md # Guidelines for Rust libraries
|
│ ├── DESIGN_DOCUMENT_LIB.md # Guidelines for Rust libraries
|
||||||
|
│ └── .gitignore # Rust-specific ignore rules
|
||||||
|
│
|
||||||
|
├── Godot/ # Godot development guidelines
|
||||||
|
│ ├── DESIGN_DOCUMENT_GODOT.md # Guidelines for Godot projects
|
||||||
|
│ └── .gitignore # Godot-specific ignore rules
|
||||||
│
|
│
|
||||||
├── Project template/ # Reusable files for new projects
|
├── Project template/ # Reusable files for new projects
|
||||||
│ ├── CHANGELOG.md # Changelog template
|
│ ├── CHANGELOG.md # Changelog template
|
||||||
│ └── PROJECT.md # Project documentation template
|
│ ├── PROJECT.md # Project documentation template
|
||||||
|
│ └── .gitignore # Shared, language-agnostic ignore rules
|
||||||
│
|
│
|
||||||
└── Zscaler/ # Corporate network setup
|
└── Zscaler/ # Corporate network setup
|
||||||
├── NODE_EXTRA_CA_CERTS.md # Instructions for Zscaler cert
|
├── ZSCALER_CERTIFICATE.md # Cert setup for Node, Python, git, cargo
|
||||||
└── ZscalerRootCertificate-2048-SHA256.crt
|
└── ZscalerRootCertificate-2048-SHA256.crt
|
||||||
```
|
```
|
||||||
|
|
||||||
## AI Coding Workflow
|
## AI Coding Workflow
|
||||||
|
|
||||||
Each project references these documents so AI assistants operate within consistent rules:
|
Every project gets two rule documents, and the split between them is strict:
|
||||||
|
|
||||||
- **AGENTS.md** — language-agnostic rules: dependency management, testing, logging, git, versioning
|
- **AGENTS.md** — language-agnostic rules only: documentation language, dependency management, static typing, secrets, git, versioning, task notation. It names no concrete tool.
|
||||||
- **DESIGN_DOCUMENT.md** — Python-specific: code style, tooling (Ruff, mypy, pytest, Poetry), project structure, logging with loguru, distribution via PyInstaller
|
- **DESIGN_DOCUMENT\*.md** — everything language-specific: package manager, formatter, linter, test framework, logging library, line length, project layout, distribution.
|
||||||
- **DESIGN_DOCUMENT_MODULE.md** — same as above, adapted for Python libraries (no sinks, no `.env`, PyPI distribution)
|
|
||||||
|
|
||||||
## Languages Covered
|
Where the two disagree, **the design document wins**.
|
||||||
|
|
||||||
| Language | Guidelines |
|
| Project type | Design document |
|
||||||
|----------|-----------|
|
|--------------|-----------------|
|
||||||
| Python (application) | `Python/DESIGN_DOCUMENT.md` |
|
| Python (application) | `Python/DESIGN_DOCUMENT.md` |
|
||||||
| Python (library) | `Python/DESIGN_DOCUMENT_MODULE.md` |
|
| Python (library) | `Python/DESIGN_DOCUMENT_MODULE.md` |
|
||||||
| Rust (application) | `Rust/DESIGN_DOCUMENT.md` |
|
| Rust (application) | `Rust/DESIGN_DOCUMENT.md` |
|
||||||
| Rust (library) | `Rust/DESIGN_DOCUMENT_LIB.md` |
|
| Rust (library) | `Rust/DESIGN_DOCUMENT_LIB.md` |
|
||||||
|
| Godot | `Godot/DESIGN_DOCUMENT_GODOT.md` |
|
||||||
|
|
||||||
|
### Starting a new project
|
||||||
|
|
||||||
|
1. Copy `Project template/PROJECT.md` and `Project template/CHANGELOG.md` into the project root — both are committed.
|
||||||
|
2. Build `.gitignore` from `Project template/.gitignore` plus the `.gitignore` of the matching language folder.
|
||||||
|
3. Copy `Claude/AGENTS.md` and the matching `DESIGN_DOCUMENT*.md` into the project root — these are **not** committed, they come from here.
|
||||||
|
4. For Python, follow `Python/TEMPLATE.md` to generate the project skeleton.
|
||||||
|
|
||||||
## Key Conventions
|
## Key Conventions
|
||||||
|
|
||||||
- **Python tooling:** Poetry · Ruff · mypy · pytest · loguru
|
- **Python tooling:** Poetry · Ruff · mypy · pytest · loguru
|
||||||
- **No print() for debugging** — loguru everywhere
|
- **Rust tooling:** Cargo · rustfmt · clippy · tracing (`thiserror` / `anyhow` for errors)
|
||||||
- **Type hints required** on all functions
|
- **Godot tooling:** gdformat · gdlint · GUT
|
||||||
- **Tests:** pytest only, no unittest, no mocks of the database
|
- **No `print()` for debugging** — the language's logging library, everywhere
|
||||||
- **Versioning:** semantic, always ask before bumping
|
- **Static typing required** on every parameter and return value
|
||||||
- **Secrets:** `.env` + `python-dotenv`, never committed
|
- **Line length:** 120 characters (Python), 100 soft limit (GDScript)
|
||||||
|
- **Tests:** the framework named in the design document — pytest (Python, never `unittest`), built-in `#[test]` (Rust), GUT (Godot)
|
||||||
|
- **Tasks:** single-line `TODO` / `FIXME` / `BUG` / `HACK` / `NOTE` comments in code — no checkboxes in documentation
|
||||||
|
- **Versioning:** semantic, always ask before bumping, `CHANGELOG.md` updated first
|
||||||
|
- **Secrets:** `.env`, never committed; libraries take configuration from the caller
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Rust — append on top of the shared core (Project template/.gitignore).
|
||||||
|
|
||||||
|
# --- Build output ---
|
||||||
|
target/
|
||||||
|
|
||||||
|
# --- Lock file ---
|
||||||
|
# Applications commit Cargo.lock — leave the line below commented out.
|
||||||
|
# Libraries do not commit it — uncomment it there.
|
||||||
|
# Cargo.lock
|
||||||
+20
-3
@@ -14,11 +14,17 @@
|
|||||||
- **PROJECT.md** — Project goals and current state
|
- **PROJECT.md** — Project goals and current state
|
||||||
- **CHANGELOG.md** — Version history
|
- **CHANGELOG.md** — Version history
|
||||||
|
|
||||||
|
### Documentation Organization
|
||||||
|
|
||||||
|
All detailed documentation of features and systems belongs in the `docs/` folder, not in the project root.
|
||||||
|
|
||||||
|
The root directory contains only the core documents: `README.md`, `AGENTS.md`, `DESIGN_DOCUMENT.md`, `PROJECT.md`, `CHANGELOG.md`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Code Style
|
## 1. Code Style
|
||||||
|
|
||||||
- **Rust edition:** 2021
|
- **Rust edition:** 2024 (requires Rust 1.85 or newer)
|
||||||
- Format with **rustfmt** — run `cargo fmt` before every commit
|
- Format with **rustfmt** — run `cargo fmt` before every commit
|
||||||
- Lint with **clippy** — run `cargo clippy -- -D warnings` before every commit
|
- Lint with **clippy** — run `cargo clippy -- -D warnings` before every commit
|
||||||
- **snake_case** functions/variables/modules, **PascalCase** types/traits, **SCREAMING_SNAKE_CASE** constants
|
- **snake_case** functions/variables/modules, **PascalCase** types/traits, **SCREAMING_SNAKE_CASE** constants
|
||||||
@@ -55,6 +61,7 @@ project/
|
|||||||
│ └── <module>/
|
│ └── <module>/
|
||||||
│ └── mod.rs
|
│ └── mod.rs
|
||||||
├── tests/ # Integration tests
|
├── tests/ # Integration tests
|
||||||
|
├── docs/ # Detailed documentation
|
||||||
├── Cargo.toml
|
├── Cargo.toml
|
||||||
└── Cargo.lock # Commit for applications, not for libraries
|
└── Cargo.lock # Commit for applications, not for libraries
|
||||||
```
|
```
|
||||||
@@ -196,10 +203,20 @@ cross build --release --target x86_64-pc-windows-gnu
|
|||||||
## 11. Documentation and Task Management
|
## 11. Documentation and Task Management
|
||||||
|
|
||||||
- Keep `PROJECT.md` and `CHANGELOG.md` up to date when making changes
|
- Keep `PROJECT.md` and `CHANGELOG.md` up to date when making changes
|
||||||
|
- Document architectural changes in this file or in `docs/`
|
||||||
|
|
||||||
### Task notation
|
### Task notation
|
||||||
|
|
||||||
|
Tasks are written as single-line comments directly in code, using the **Todo Tree** tags defined in `AGENTS.md` (`TODO`, `FIXME`, `BUG`, `HACK`, `NOTE`). `PROJECT.md` carries only cross-cutting tasks that have no single place in the code.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// TODO: one-liner description of a task to be done
|
// TODO: extract this into a separate module
|
||||||
// FIXME: one-liner description of a known bug to be fixed
|
// FIXME: panics on an empty slice
|
||||||
|
// BUG: off-by-one when the buffer is exactly full
|
||||||
|
// HACK: temporary workaround until the crate adds paging
|
||||||
|
// NOTE: order matters here, the parser is stateful
|
||||||
```
|
```
|
||||||
|
|
||||||
|
No other task format is used — **no checkboxes, no numbered lists in documentation**.
|
||||||
|
|
||||||
|
If a tag already exists at a specific location in code, do not repeat it in `PROJECT.md`.
|
||||||
|
|||||||
+34
-14
@@ -14,11 +14,20 @@
|
|||||||
- **PROJECT.md** — Project goals and current state
|
- **PROJECT.md** — Project goals and current state
|
||||||
- **CHANGELOG.md** — Version history
|
- **CHANGELOG.md** — Version history
|
||||||
|
|
||||||
|
### Documentation Organization
|
||||||
|
|
||||||
|
All detailed documentation of features and systems belongs in the `docs/` folder, not in the project root.
|
||||||
|
|
||||||
|
The API reference is generated by `cargo doc` from doc comments — `docs/` holds the prose documentation that does not fit in doc comments.
|
||||||
|
|
||||||
|
The root directory contains only the core documents: `README.md`, `AGENTS.md`, `DESIGN_DOCUMENT_LIB.md`, `PROJECT.md`, `CHANGELOG.md`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Code Style
|
## 1. Code Style
|
||||||
|
|
||||||
- **Rust edition:** 2021
|
- **Rust edition:** 2024 (requires Rust 1.85 or newer)
|
||||||
|
- Declare the minimum toolchain in `Cargo.toml` (`rust-version = "1.85"`) so consumers get a clear error instead of a compile failure
|
||||||
- Format with **rustfmt** — run `cargo fmt` before every commit
|
- Format with **rustfmt** — run `cargo fmt` before every commit
|
||||||
- Lint with **clippy** — run `cargo clippy -- -D warnings` before every commit
|
- Lint with **clippy** — run `cargo clippy -- -D warnings` before every commit
|
||||||
- **snake_case** functions/variables/modules, **PascalCase** types/traits, **SCREAMING_SNAKE_CASE** constants
|
- **snake_case** functions/variables/modules, **PascalCase** types/traits, **SCREAMING_SNAKE_CASE** constants
|
||||||
@@ -43,7 +52,7 @@ Never edit `Cargo.toml` dependency versions by hand — use `cargo add`.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Project Structure
|
## 3. Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
project/
|
project/
|
||||||
@@ -54,6 +63,7 @@ project/
|
|||||||
│ └── mod.rs
|
│ └── mod.rs
|
||||||
├── tests/ # Integration tests (test the public API only)
|
├── tests/ # Integration tests (test the public API only)
|
||||||
├── examples/ # Usage examples
|
├── examples/ # Usage examples
|
||||||
|
├── docs/ # Detailed documentation
|
||||||
├── Cargo.toml
|
├── Cargo.toml
|
||||||
└── Cargo.lock # Do NOT commit — add to .gitignore
|
└── Cargo.lock # Do NOT commit — add to .gitignore
|
||||||
```
|
```
|
||||||
@@ -62,7 +72,7 @@ No `main.rs` — libraries have no entry point.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Public API
|
## 4. Public API
|
||||||
|
|
||||||
- Everything intended for external use must be `pub` and re-exported from `lib.rs`
|
- Everything intended for external use must be `pub` and re-exported from `lib.rs`
|
||||||
- Use `pub(crate)` for internal items that cross module boundaries
|
- Use `pub(crate)` for internal items that cross module boundaries
|
||||||
@@ -79,7 +89,7 @@ pub use types::{Config, Response};
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Error Handling
|
## 5. Error Handling
|
||||||
|
|
||||||
- Define all public error types in `src/error.rs` using **thiserror**
|
- Define all public error types in `src/error.rs` using **thiserror**
|
||||||
- Export all errors from `lib.rs`
|
- Export all errors from `lib.rs`
|
||||||
@@ -101,7 +111,7 @@ pub enum MyError {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Logging
|
## 6. Logging
|
||||||
|
|
||||||
This is a library. Libraries must **never configure logging sinks** — that is the responsibility of the consuming application.
|
This is a library. Libraries must **never configure logging sinks** — that is the responsibility of the consuming application.
|
||||||
|
|
||||||
@@ -134,13 +144,13 @@ Never call `tracing_subscriber::fmt().init()` or any sink setup inside library c
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Environment and Secrets
|
## 7. Environment and Secrets
|
||||||
|
|
||||||
Libraries do not read environment variables or `.env` files. Configuration is passed by the caller via arguments or constructor parameters.
|
Libraries do not read environment variables or `.env` files. Configuration is passed by the caller via arguments or constructor parameters.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Testing
|
## 8. Testing
|
||||||
|
|
||||||
- Use Rust's built-in test framework — `#[test]` and `#[cfg(test)]`
|
- Use Rust's built-in test framework — `#[test]` and `#[cfg(test)]`
|
||||||
- Unit tests live in the same file as the code, in a `mod tests` block
|
- Unit tests live in the same file as the code, in a `mod tests` block
|
||||||
@@ -163,7 +173,7 @@ mod tests {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. Documentation
|
## 9. Documentation
|
||||||
|
|
||||||
All public items must have doc comments. Use `cargo doc --open` to verify locally.
|
All public items must have doc comments. Use `cargo doc --open` to verify locally.
|
||||||
|
|
||||||
@@ -187,7 +197,7 @@ pub fn parse(s: &str) -> Result<u32, MyError> { ... }
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 9. Tooling
|
## 10. Tooling
|
||||||
|
|
||||||
| Tool | Purpose |
|
| Tool | Purpose |
|
||||||
|------|---------|
|
|------|---------|
|
||||||
@@ -205,7 +215,7 @@ cargo test
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 10. Distribution
|
## 11. Distribution
|
||||||
|
|
||||||
Build and publish with Cargo:
|
Build and publish with Cargo:
|
||||||
|
|
||||||
@@ -218,7 +228,7 @@ cargo publish # Publish to crates.io (requires login)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 11. Versioning
|
## 12. Versioning
|
||||||
|
|
||||||
- Follow **semantic versioning**: `MAJOR.MINOR.PATCH`
|
- Follow **semantic versioning**: `MAJOR.MINOR.PATCH`
|
||||||
- Version is defined in `Cargo.toml` under `[package]`
|
- Version is defined in `Cargo.toml` under `[package]`
|
||||||
@@ -228,14 +238,24 @@ cargo publish # Publish to crates.io (requires login)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 12. Documentation and Task Management
|
## 13. Documentation and Task Management
|
||||||
|
|
||||||
- Keep `PROJECT.md` and `CHANGELOG.md` up to date when making changes
|
- Keep `PROJECT.md` and `CHANGELOG.md` up to date when making changes
|
||||||
|
- Document architectural changes in this file or in `docs/`
|
||||||
- `README.md` must contain installation instructions and usage examples for the public API
|
- `README.md` must contain installation instructions and usage examples for the public API
|
||||||
|
|
||||||
### Task notation
|
### Task notation
|
||||||
|
|
||||||
|
Tasks are written as single-line comments directly in code, using the **Todo Tree** tags defined in `AGENTS.md` (`TODO`, `FIXME`, `BUG`, `HACK`, `NOTE`). `PROJECT.md` carries only cross-cutting tasks that have no single place in the code.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// TODO: one-liner description of a task to be done
|
// TODO: extract this into a separate module
|
||||||
// FIXME: one-liner description of a known bug to be fixed
|
// FIXME: panics on an empty slice
|
||||||
|
// BUG: off-by-one when the buffer is exactly full
|
||||||
|
// HACK: temporary workaround until the crate adds paging
|
||||||
|
// NOTE: order matters here, the parser is stateful
|
||||||
```
|
```
|
||||||
|
|
||||||
|
No other task format is used — **no checkboxes, no numbered lists in documentation**.
|
||||||
|
|
||||||
|
If a tag already exists at a specific location in code, do not repeat it in `PROJECT.md`.
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
## NODE_EXTRA_CA_CERTS
|
|
||||||
|
|
||||||
1. `Win + R` → `sysdm.cpl` → **Advanced → Environment Variables**
|
|
||||||
2. Pod **User variables** → **New**
|
|
||||||
- Name: `NODE_EXTRA_CA_CERTS`
|
|
||||||
- Value: `C:\cesta\k\zscaler.cer`
|
|
||||||
3. OK → restartovat VSCode
|
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# Zscaler Root Certificate
|
||||||
|
|
||||||
|
On the corporate network Zscaler terminates TLS and re-signs traffic with its own root
|
||||||
|
certificate. Every tool that ships its own certificate store — Node, Python, git, cargo —
|
||||||
|
rejects those connections until the Zscaler root is trusted explicitly.
|
||||||
|
|
||||||
|
Typical symptoms: `unable to get local issuer certificate`, `SELF_SIGNED_CERT_IN_CHAIN`,
|
||||||
|
`SSL: CERTIFICATE_VERIFY_FAILED`, `server certificate verification failed`.
|
||||||
|
|
||||||
|
## The certificate
|
||||||
|
|
||||||
|
The root certificate ships in this folder as `ZscalerRootCertificate-2048-SHA256.crt`.
|
||||||
|
It is PEM-encoded — the `.crt` / `.cer` / `.pem` extension makes no difference, the tools
|
||||||
|
below only care about the content.
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|-------|-------|
|
||||||
|
| Subject | `CN=Zscaler Root CA, O=Zscaler Inc.` |
|
||||||
|
| Valid until | 2042-05-06 |
|
||||||
|
| SHA-256 | `04:F6:1F:1D:13:AA:E1:D1:65:73:DC:2C:37:F7:96:FD:F4:AC:97:71:3A:69:59:EB:B1:1D:24:73:95:8B:1A:53` |
|
||||||
|
|
||||||
|
Copy it to a permanent location outside any repository — the rest of this document assumes
|
||||||
|
`C:\certs\ZscalerRootCertificate-2048-SHA256.crt`.
|
||||||
|
|
||||||
|
Verify the copy before trusting it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl x509 -in C:/certs/ZscalerRootCertificate-2048-SHA256.crt -noout -fingerprint -sha256
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment variables
|
||||||
|
|
||||||
|
Set these as **user** variables on Windows:
|
||||||
|
|
||||||
|
1. `Win + R` → `sysdm.cpl` → **Advanced → Environment Variables**
|
||||||
|
2. Under **User variables** → **New**
|
||||||
|
3. Enter the name and value from the table
|
||||||
|
4. OK → restart VS Code and every open terminal, otherwise the old value stays in effect
|
||||||
|
|
||||||
|
| Variable | Used by | Value |
|
||||||
|
|----------|---------|-------|
|
||||||
|
| `NODE_EXTRA_CA_CERTS` | Node.js, npm, Electron, VS Code extensions | `C:\certs\ZscalerRootCertificate-2048-SHA256.crt` |
|
||||||
|
| `REQUESTS_CA_BUNDLE` | Python `requests`, Poetry, pip | same path |
|
||||||
|
| `SSL_CERT_FILE` | OpenSSL, Python `ssl` / `httpx` / `aiohttp` | same path |
|
||||||
|
| `CURL_CA_BUNDLE` | curl | same path |
|
||||||
|
| `CARGO_HTTP_CAINFO` | cargo, crates.io | same path |
|
||||||
|
|
||||||
|
`SSL_CERT_FILE` covers most Python clients, but `requests` prefers `REQUESTS_CA_BUNDLE` —
|
||||||
|
set both.
|
||||||
|
|
||||||
|
## Git
|
||||||
|
|
||||||
|
Git reads none of those variables. Configure it directly, with **forward slashes** — git
|
||||||
|
treats a backslash in a config value as an escape character:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git config --global http.sslCAInfo "C:/certs/ZscalerRootCertificate-2048-SHA256.crt"
|
||||||
|
```
|
||||||
|
|
||||||
|
Never use `http.sslVerify=false` as a workaround — it disables verification for every
|
||||||
|
remote, not just the ones behind Zscaler.
|
||||||
|
|
||||||
|
## Verifying the setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sSI https://pypi.org | head -1
|
||||||
|
git ls-remote https://github.com/git/git HEAD
|
||||||
|
node -e "require('https').get('https://registry.npmjs.org', r => console.log(r.statusCode))"
|
||||||
|
poetry run python -c "import requests; print(requests.get('https://pypi.org', timeout=10).status_code)"
|
||||||
|
```
|
||||||
|
|
||||||
|
All four must succeed without a certificate error.
|
||||||
Reference in New Issue
Block a user