From cb3549edd180ff186269589d3ddf999b9f6de0b4 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Wed, 25 Mar 2026 23:48:07 +0000 Subject: [PATCH 1/2] copier --- .copier-answers.yml | 2 +- .devcontainer/devcontainer.json | 2 +- .devcontainer/install-ci-tooling.py | 2 +- .../build-docker-image.yaml | 11 +++++++--- AGENTS.md | 22 ++++++++++++++++++- extensions/context.py | 4 ++-- pyproject.toml | 2 +- template/AGENTS.md | 22 ++++++++++++++++++- uv.lock | 8 +++---- 9 files changed, 60 insertions(+), 15 deletions(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index b79585d7..681ffe7c 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: v0.0.104 +_commit: v0.0.106 _src_path: gh:LabAutomationAndScreening/copier-base-template.git description: Copier template for creating Python libraries and executables install_claude_cli: true diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 5b3fb147..e7a97d64 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -61,5 +61,5 @@ "initializeCommand": "sh .devcontainer/initialize-command.sh", "onCreateCommand": "sh .devcontainer/on-create-command.sh", "postStartCommand": "sh .devcontainer/post-start-command.sh" - // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): 0f1c7f94 # spellchecker:disable-line + // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): c4997eda # spellchecker:disable-line } diff --git a/.devcontainer/install-ci-tooling.py b/.devcontainer/install-ci-tooling.py index 6f90796d..6e6f54fb 100644 --- a/.devcontainer/install-ci-tooling.py +++ b/.devcontainer/install-ci-tooling.py @@ -7,7 +7,7 @@ import tempfile from pathlib import Path -UV_VERSION = "0.10.10" +UV_VERSION = "0.10.12" PNPM_VERSION = "10.32.1" COPIER_VERSION = "==9.14.0" COPIER_TEMPLATE_EXTENSIONS_VERSION = "==0.3.3" diff --git a/.github/reusable_workflows/build-docker-image.yaml b/.github/reusable_workflows/build-docker-image.yaml index cfbdc543..91918b7c 100644 --- a/.github/reusable_workflows/build-docker-image.yaml +++ b/.github/reusable_workflows/build-docker-image.yaml @@ -35,6 +35,9 @@ on: artifact-name: description: 'The name of the uploaded artifact of the image tarball' value: ${{ jobs.build-image.outputs.artifact-name }} + full-image-tag: + description: 'The full image tag used for the built image (repository/name:context-hash)' + value: ${{ jobs.build-image.outputs.full-image-tag }} permissions: id-token: write @@ -47,6 +50,7 @@ jobs: runs-on: ubuntu-24.04 outputs: artifact-name: ${{ steps.calculate-build-context-hash.outputs.image_name_no_slashes }} + full-image-tag: ${{ steps.calculate-build-context-hash.outputs.full_image_tag }} steps: - name: Parse ECR URL if: ${{ inputs.push-role-name != 'no-push' }} @@ -89,6 +93,7 @@ jobs: IMAGE_NAME_NO_SLASHES="${IMAGE_NAME_WITH_NAMESPACE//\//-}" echo "image_name_no_slashes=${IMAGE_NAME_NO_SLASHES}" >> "$GITHUB_OUTPUT" echo "Image name without slashes: ${IMAGE_NAME_NO_SLASHES}" + echo "full_image_tag=${{ inputs.repository }}/${{ inputs.image_name }}:context-${BUILD_HASH}" >> "$GITHUB_OUTPUT" - name: Set up mutex # Github concurrency management is horrible, things get arbitrarily cancelled if queued up. So using mutex until github fixes itself. When multiple jobs are modifying cache at once, weird things can happen. possible issue is https://github.com/actions/toolkit/issues/658 if: ${{ inputs.push-role-name != 'no-push' }} @@ -114,7 +119,7 @@ jobs: - name: Pull existing image to package as artifact if: ${{ inputs.save-as-artifact && steps.check-if-exists.outputs.status == 'found' }} run: | - docker pull ${{ inputs.repository }}/${{ inputs.image_name }}:${{ steps.calculate-build-context-hash.outputs.build_context_tag }} + docker pull ${{ steps.calculate-build-context-hash.outputs.full_image_tag }} - name: Set up Docker Buildx if: ${{ (inputs.save-as-artifact && inputs.push-role-name == 'no-push') || steps.check-if-exists.outputs.status == 'notfound' }} @@ -129,7 +134,7 @@ jobs: context: ${{ inputs.context }} push: ${{ inputs.push-role-name != 'no-push' && steps.check-if-exists.outputs.status == 'notfound' }} load: ${{ inputs.save-as-artifact }} # make the image available later for the `docker save` step - tags: ${{ inputs.repository }}/${{ inputs.image_name }}:${{ steps.calculate-build-context-hash.outputs.build_context_tag }} + tags: ${{ steps.calculate-build-context-hash.outputs.full_image_tag }} - name: Add git sha tag if: ${{ inputs.push-role-name != 'no-push' }} @@ -147,7 +152,7 @@ jobs: - name: Save Docker Image as tar if: ${{ inputs.save-as-artifact }} - run: docker save -o ${{ steps.calculate-build-context-hash.outputs.image_name_no_slashes }}.tar ${{ inputs.repository }}/${{ inputs.image_name }}:${{ steps.calculate-build-context-hash.outputs.build_context_tag }} + run: docker save -o ${{ steps.calculate-build-context-hash.outputs.image_name_no_slashes }}.tar ${{ steps.calculate-build-context-hash.outputs.full_image_tag }} - name: Upload Docker Image Artifact if: ${{ inputs.save-as-artifact }} diff --git a/AGENTS.md b/AGENTS.md index 2f443460..f2cbccb6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,4 +1,11 @@ +# Project Structure + +This project is a Copier template used to generate other copier templates. It is the "grandparent" of actual instantiated application/library repositories. + +# Code Guidelines + ## Code Style + - Comments should be used very rarely. Code should generally express its intent. - Never write a one-line docstring — either the name is sufficient or the behavior warrants a full explanation. - Don't sort or remove imports manually — pre-commit handles it. @@ -6,8 +13,10 @@ - Respect the pyright rule reportUnusedCallResult; assign unneeded return values to `_` - Prefer keyword-only parameters (unless a very clear single-argument function): use `*` in Python signatures and destructured options objects in TypeScript. - When disabling a linting rule with an inline directive, provide a comment at the end of the line (or on the line above for tools that don't allow extra text after an inline directive) describing the reasoning for disabling the rule. +- Avoid telling the type checker what a type is rather than letting it prove it. This includes type assertions (`as SomeType` in TypeScript, `cast()` in Python) and variable annotations that override inference. Prefer approaches that let the type checker verify the type itself: `isinstance`/`instanceof` narrowing, restructuring code so the correct type flows naturally, or using discriminated unions. When there is genuinely no alternative, add a comment explaining why the workaround is necessary and why it is safe. ## Testing + - Always run tests with an explicit path (e.g. uv run pytest tests/unit) — test runners discover all types by default. - Test coverage requirements are usually at 100%, so when running a subset of tests, always disable test coverage to avoid the test run failing for insufficient coverage. - Avoid magic values in comparisons in tests in all languages (like ruff rule PLR2004 specifies) @@ -16,22 +25,33 @@ - Key `data-testid` selectors off unique IDs (e.g. UUIDs), not human-readable names which may collide or change. ### Python Testing + - When using `mocker.spy` on a class-level method (including inherited ones), the spy records the unbound call, so assertions need `ANY` as the first argument to match self: `spy.assert_called_once_with(ANY, expected_arg)` - Before writing new mock/spy helpers, check the `tests/unit/` folder for pre-built helpers in files like `fixtures.py` or `*mocks.py` - When a test needs a fixture only for its side effects (not its return value), use `@pytest.mark.usefixtures(fixture_name.__name__)` instead of adding an unused parameter with a noqa comment - Use `__name__` instead of string literals when referencing functions/methods (e.g., `mocker.patch.object(MyClass, MyClass.method.__name__)`, `pytest.mark.usefixtures(my_fixture.__name__)`). This enables IDE refactoring tools to catch renames. +- When using the faker library, prefer the pytest fixture (provided by the faker library) over instantiating instances of Faker. - **Never hand-write VCR cassette YAML files.** Cassettes must be recorded from real HTTP interactions by running the test once with `--record-mode=once` against a live external service: `uv run pytest --record-mode=once --no-cov`. The default mode is `none` — a missing cassette will cause an error, which is expected until recorded. - **Never hand-edit syrupy snapshot files.** Snapshots are auto-generated — to create or update them, run `uv run pytest --snapshot-update --no-cov`. A missing snapshot causes the test to fail, which is expected until you run with `--snapshot-update`. When a snapshot mismatch occurs, fix the code if the change was unintentional; run `--snapshot-update` if it was intentional. - **Never hand-write or hand-edit pytest-reserial `.jsonl` recording files.** Recordings must be captured from real serial port traffic by running the test with `--record` while the device is connected: `uv run pytest --record --no-cov`. The default mode replays recordings — a missing recording causes an error, which is expected until recorded against a live device. +# Agent Implementations & Configurations + +## Memory and Rules + +- Before saving any memory or adding any rule, explicitly ask the user whether the concept should be: (1) added to AGENTS.md as a general rule applicable across all projects, (2) added to AGENTS.md as a rule specific to this project, or (3) stored as a temporary local memory only relevant to the current active work. The devcontainer environment is ephemeral, so local memory files are rarely the right choice. ## Tooling + - Always use `uv run python` instead of `python3` or `python` when running Python commands. - Prefer dedicated shell tools over `python3`/`python` for simple one-off tasks: use `jq` for JSON parsing, standard shell builtins for string manipulation, etc. Only reach for `python3` when no simpler tool covers the need. - Check .devcontainer/devcontainer.json for tooling versions (Python, Node, etc.) when reasoning about version-specific stdlib or tooling behavior. -- For frontend work, run commands via `pnpm` scripts from `frontend/package.json` — never invoke tools directly (not pnpm exec , npx , etc.). ✅ pnpm test-unit ❌ pnpm vitest ... or npx vitest ... +- For frontend tests, run commands via `pnpm` scripts from `frontend/package.json` — never invoke tools directly (not pnpm exec , npx , etc.). ✅ pnpm test-unit ❌ pnpm vitest ... or npx vitest ... +- For linting and type-checking, prefer `pre-commit run ` over invoking tools directly — this matches the permission allow-list and mirrors what CI runs. Key hook IDs: `typescript-check`, `eslint`, `pyright`, `ruff`, `ruff-format`. +- Never rely on IDE diagnostics for ruff warnings — the IDE may not respect the project's ruff.toml config. Run `pre-commit run ruff -a` to get accurate results. - When running terminal commands, execute exactly one command per tool call. Do not chain commands with &&, ||, ;, or & — this prohibition has no exceptions, even for `cd && ...` patterns. Use absolute paths instead of `cd` to avoid needing to chain. Pipes (|) are allowed for output transformation (e.g., head, tail, grep). If two sequential commands are needed, run them in separate tool calls. Chained commands break the permission allow-list matcher and cause unnecessary permission prompts - Never use backslash line continuations in shell commands — always write the full command on a single line. Backslashes break the permission allow-list matcher. +- **Never manually edit files in any `generated/` folder.** These files are produced by codegen tooling (typically Kiota) and any manual changes will be overwritten. If a generated file needs to change, update the source (e.g. the OpenAPI schema) and re-run the generator. ## Issue Tracking with bd (beads) diff --git a/extensions/context.py b/extensions/context.py index 6e2bb1b5..b673a7ef 100644 --- a/extensions/context.py +++ b/extensions/context.py @@ -10,13 +10,13 @@ class ContextUpdater(ContextHook): @override def hook(self, context: dict[Any, Any]) -> dict[Any, Any]: - context["uv_version"] = "0.10.10" + context["uv_version"] = "0.10.12" context["pnpm_version"] = "10.32.1" context["pre_commit_version"] = "4.5.1" context["pyright_version"] = ">=1.1.408" context["pytest_version"] = ">=9.0.2" context["pytest_randomly_version"] = ">=4.0.1" - context["pytest_cov_version"] = ">=7.0.0" + context["pytest_cov_version"] = ">=7.1.0" context["ty_version"] = ">=0.0.23" context["copier_version"] = "==9.14.0" context["copier_template_extensions_version"] = "==0.3.3" diff --git a/pyproject.toml b/pyproject.toml index 5fd41a98..06b860e9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ dependencies = [ # Managed by upstream template "pytest>=9.0.2", - "pytest-cov>=7.0.0", + "pytest-cov>=7.1.0", "pytest-randomly>=4.0.1", "pyright[nodejs]>=1.1.408", "ty>=0.0.23", diff --git a/template/AGENTS.md b/template/AGENTS.md index 2f443460..f2cbccb6 100644 --- a/template/AGENTS.md +++ b/template/AGENTS.md @@ -1,4 +1,11 @@ +# Project Structure + +This project is a Copier template used to generate other copier templates. It is the "grandparent" of actual instantiated application/library repositories. + +# Code Guidelines + ## Code Style + - Comments should be used very rarely. Code should generally express its intent. - Never write a one-line docstring — either the name is sufficient or the behavior warrants a full explanation. - Don't sort or remove imports manually — pre-commit handles it. @@ -6,8 +13,10 @@ - Respect the pyright rule reportUnusedCallResult; assign unneeded return values to `_` - Prefer keyword-only parameters (unless a very clear single-argument function): use `*` in Python signatures and destructured options objects in TypeScript. - When disabling a linting rule with an inline directive, provide a comment at the end of the line (or on the line above for tools that don't allow extra text after an inline directive) describing the reasoning for disabling the rule. +- Avoid telling the type checker what a type is rather than letting it prove it. This includes type assertions (`as SomeType` in TypeScript, `cast()` in Python) and variable annotations that override inference. Prefer approaches that let the type checker verify the type itself: `isinstance`/`instanceof` narrowing, restructuring code so the correct type flows naturally, or using discriminated unions. When there is genuinely no alternative, add a comment explaining why the workaround is necessary and why it is safe. ## Testing + - Always run tests with an explicit path (e.g. uv run pytest tests/unit) — test runners discover all types by default. - Test coverage requirements are usually at 100%, so when running a subset of tests, always disable test coverage to avoid the test run failing for insufficient coverage. - Avoid magic values in comparisons in tests in all languages (like ruff rule PLR2004 specifies) @@ -16,22 +25,33 @@ - Key `data-testid` selectors off unique IDs (e.g. UUIDs), not human-readable names which may collide or change. ### Python Testing + - When using `mocker.spy` on a class-level method (including inherited ones), the spy records the unbound call, so assertions need `ANY` as the first argument to match self: `spy.assert_called_once_with(ANY, expected_arg)` - Before writing new mock/spy helpers, check the `tests/unit/` folder for pre-built helpers in files like `fixtures.py` or `*mocks.py` - When a test needs a fixture only for its side effects (not its return value), use `@pytest.mark.usefixtures(fixture_name.__name__)` instead of adding an unused parameter with a noqa comment - Use `__name__` instead of string literals when referencing functions/methods (e.g., `mocker.patch.object(MyClass, MyClass.method.__name__)`, `pytest.mark.usefixtures(my_fixture.__name__)`). This enables IDE refactoring tools to catch renames. +- When using the faker library, prefer the pytest fixture (provided by the faker library) over instantiating instances of Faker. - **Never hand-write VCR cassette YAML files.** Cassettes must be recorded from real HTTP interactions by running the test once with `--record-mode=once` against a live external service: `uv run pytest --record-mode=once --no-cov`. The default mode is `none` — a missing cassette will cause an error, which is expected until recorded. - **Never hand-edit syrupy snapshot files.** Snapshots are auto-generated — to create or update them, run `uv run pytest --snapshot-update --no-cov`. A missing snapshot causes the test to fail, which is expected until you run with `--snapshot-update`. When a snapshot mismatch occurs, fix the code if the change was unintentional; run `--snapshot-update` if it was intentional. - **Never hand-write or hand-edit pytest-reserial `.jsonl` recording files.** Recordings must be captured from real serial port traffic by running the test with `--record` while the device is connected: `uv run pytest --record --no-cov`. The default mode replays recordings — a missing recording causes an error, which is expected until recorded against a live device. +# Agent Implementations & Configurations + +## Memory and Rules + +- Before saving any memory or adding any rule, explicitly ask the user whether the concept should be: (1) added to AGENTS.md as a general rule applicable across all projects, (2) added to AGENTS.md as a rule specific to this project, or (3) stored as a temporary local memory only relevant to the current active work. The devcontainer environment is ephemeral, so local memory files are rarely the right choice. ## Tooling + - Always use `uv run python` instead of `python3` or `python` when running Python commands. - Prefer dedicated shell tools over `python3`/`python` for simple one-off tasks: use `jq` for JSON parsing, standard shell builtins for string manipulation, etc. Only reach for `python3` when no simpler tool covers the need. - Check .devcontainer/devcontainer.json for tooling versions (Python, Node, etc.) when reasoning about version-specific stdlib or tooling behavior. -- For frontend work, run commands via `pnpm` scripts from `frontend/package.json` — never invoke tools directly (not pnpm exec , npx , etc.). ✅ pnpm test-unit ❌ pnpm vitest ... or npx vitest ... +- For frontend tests, run commands via `pnpm` scripts from `frontend/package.json` — never invoke tools directly (not pnpm exec , npx , etc.). ✅ pnpm test-unit ❌ pnpm vitest ... or npx vitest ... +- For linting and type-checking, prefer `pre-commit run ` over invoking tools directly — this matches the permission allow-list and mirrors what CI runs. Key hook IDs: `typescript-check`, `eslint`, `pyright`, `ruff`, `ruff-format`. +- Never rely on IDE diagnostics for ruff warnings — the IDE may not respect the project's ruff.toml config. Run `pre-commit run ruff -a` to get accurate results. - When running terminal commands, execute exactly one command per tool call. Do not chain commands with &&, ||, ;, or & — this prohibition has no exceptions, even for `cd && ...` patterns. Use absolute paths instead of `cd` to avoid needing to chain. Pipes (|) are allowed for output transformation (e.g., head, tail, grep). If two sequential commands are needed, run them in separate tool calls. Chained commands break the permission allow-list matcher and cause unnecessary permission prompts - Never use backslash line continuations in shell commands — always write the full command on a single line. Backslashes break the permission allow-list matcher. +- **Never manually edit files in any `generated/` folder.** These files are produced by codegen tooling (typically Kiota) and any manual changes will be overwritten. If a generated file needs to change, update the source (e.g. the OpenAPI schema) and re-run the generator. ## Issue Tracking with bd (beads) diff --git a/uv.lock b/uv.lock index 10ee99e8..7f48f129 100644 --- a/uv.lock +++ b/uv.lock @@ -64,7 +64,7 @@ requires-dist = [ { name = "copier-template-extensions", specifier = "==0.3.3" }, { name = "pyright", extras = ["nodejs"], specifier = ">=1.1.408" }, { name = "pytest", specifier = ">=9.0.2" }, - { name = "pytest-cov", specifier = ">=7.0.0" }, + { name = "pytest-cov", specifier = ">=7.1.0" }, { name = "pytest-randomly", specifier = ">=4.0.1" }, { name = "ty", specifier = ">=0.0.23" }, ] @@ -420,16 +420,16 @@ wheels = [ [[package]] name = "pytest-cov" -version = "7.0.0" +version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage" }, { name = "pluggy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, ] [[package]] From cc64263178f46a8a1816b76022a2e20743051ed2 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Wed, 25 Mar 2026 23:49:45 +0000 Subject: [PATCH 2/2] structure --- template/AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/AGENTS.md b/template/AGENTS.md index f2cbccb6..ca13fffa 100644 --- a/template/AGENTS.md +++ b/template/AGENTS.md @@ -1,6 +1,6 @@ # Project Structure -This project is a Copier template used to generate other copier templates. It is the "grandparent" of actual instantiated application/library repositories. +This project is a Python library. # Code Guidelines