Skip to content

Commit d96e448

Browse files
committed
copier
1 parent 736729e commit d96e448

File tree

19 files changed

+174
-19
lines changed

19 files changed

+174
-19
lines changed

.claude/commands/red.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,21 @@ This phase is **not part of the regular TDD workflow** and must only be applied
9494
- Once sufficient understanding is achieved, all spike code is discarded, and normal TDD resumes starting from the **Red Phase**.
9595
- A Spike is justified only when it is impossible to define a meaningful failing test due to technical uncertainty or unknown system behavior.
9696

97+
### If a New Test Passes Immediately
98+
99+
If a newly written test passes without any implementation change, do not assume it is correct. Verify it actually exercises the intended behavior:
100+
101+
1. Identify the implementation line most likely responsible for the pass
102+
2. Temporarily remove that line
103+
3. Run the **full test suite** (not just the new test)
104+
105+
Then interpret the result:
106+
107+
- **Only the new test fails** — the line was never driven by a prior test. This is accidental over-implementation: delete the line permanently and proceed to the green phase to reintroduce it properly.
108+
- **Other existing tests also fail** — the line was already legitimately required by prior work. The new test is valid regression coverage. Restore the line; the test is confirmed correct as written.
109+
110+
In both cases, confirm the new test fails for the expected reason before proceeding (the right assertion, not a syntax or import error).
111+
97112
### General Information
98113

99114
- Sometimes the test output shows as no tests have been run when a new test is failing due to a missing import or constructor. In such cases, allow the agent to create simple stubs. Ask them if they forgot to create a stub if they are stuck.

.claude/settings/permissions/bash.jsonc

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,17 @@
7474
"Bash(tail *)",
7575
// Search
7676
"Bash(rg *)",
77+
// Research
78+
"Bash(gh issue list *)",
79+
"Bash(gh pr view *)",
80+
"Bash(gh pr diff *)"
7781
],
7882
"ask": [
79-
"Bash(gh *)", // let's hold off before we let it use the github CLI in any free running allow mode...I don't want it somehow approving PRs with the user's credentials
83+
// let's hold off before we let it use the github CLI in any free running allow mode...I don't want it somehow approving PRs with the user's credentials
84+
"Bash(gh repo *)",
85+
"Bash(gh release *)",
86+
"Bash(gh secret *)",
87+
"Bash(gh ruleset *)",
8088
"Bash(aws *)", // let's hold off before we let it use AWS CLI in any free running allow mode. We need to be very sure we don't have any access to staging or production credentials in our dev environment (...which we shouldn't...but we need to double check that or consider any other safeguards first)
8189
"Bash(curl *)",
8290
"Bash(ln *)",
@@ -85,6 +93,17 @@
8593
"deny": [
8694
// Exceptions to generally allowed AI tooling
8795
"Bash(bd init*)", // we need to control the init process, don't let AI do that in the background
96+
// Github
97+
// Claude should not ever interfere with the PR process, that is how we gate AI's work
98+
"Bash(gh pr create *)",
99+
"Bash(gh pr edit *)",
100+
"Bash(gh pr ready *)",
101+
"Bash(gh pr review *)",
102+
"Bash(gh pr merge *)",
103+
"Bash(gh pr close *)",
104+
"Bash(gh pr comment *)",
105+
"Bash(gh pr update-branch *)",
106+
88107
// Destructive File Operations
89108
"Bash(chmod -R *)",
90109
"Bash(chown -R *)",

.coderabbit.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ reviews:
77
instructions: "These files came from a vendor and we're not allowed to change them. Refer to it if you need to understand how the main code interacts with it, but do not make comments about it."
88
- path: "**/*.py"
99
instructions: "Check the `ruff.toml` and `ruff-test.toml` for linting rules we've explicitly disabled and don't suggest changes to please conventions we've disabled. Do not express concerns about ruff rules; a pre-commit hook already runs a ruff check. Do not warn about unnecessary super().__init__() calls; pyright prefers those to be present. Do not warn about missing type hints; a pre-commit hook already checks for that."
10+
- path: "**/.copier-answers.yml"
11+
instructions: "Do not comment about the `_commit` value needing to be a clean release tag. A CI job will fail if that is not the case."
1012
tools:
1113
eslint: # when the code contains typescript, eslint will be run by pre-commit, and coderabbit often generates false positives
1214
enabled: false

.copier-answers.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Changes here will be overwritten by Copier
2-
_commit: v0.0.107
2+
_commit: v0.0.110
33
_src_path: gh:LabAutomationAndScreening/copier-base-template.git
44
description: A web app that is hosted within a local intranet. Nuxt frontend, python
55
backend, docker-compose

.devcontainer/devcontainer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,5 @@
6565
"initializeCommand": "sh .devcontainer/initialize-command.sh",
6666
"onCreateCommand": "sh .devcontainer/on-create-command.sh",
6767
"postStartCommand": "sh .devcontainer/post-start-command.sh"
68-
// Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): 80d9f36a # spellchecker:disable-line
68+
// Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): 69f80248 # spellchecker:disable-line
6969
}

.devcontainer/manual-setup-deps.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
REPO_ROOT_DIR = Path(__file__).parent.parent.resolve()
1313
ENVS_CONFIG = REPO_ROOT_DIR / ".devcontainer" / "envs.json"
14+
PULUMI_CLI_INSTALL_SCRIPT = REPO_ROOT_DIR / ".devcontainer" / "install-pulumi-cli.sh"
1415
UV_PYTHON_ALREADY_CONFIGURED = "UV_PYTHON" in os.environ
1516
parser = argparse.ArgumentParser(description="Manual setup for dependencies in the repo")
1617
_ = parser.add_argument(
@@ -44,6 +45,12 @@
4445
default=False,
4546
help="Allow uv to install new versions of Python on the fly. This is typically only needed when instantiating the copier template.",
4647
)
48+
_ = parser.add_argument(
49+
"--skip-installing-pulumi-cli",
50+
action="store_true",
51+
default=False,
52+
help="Do not install the Pulumi CLI even if the lock file references it",
53+
)
4754

4855

4956
class PackageManager(str, enum.Enum):
@@ -127,6 +134,22 @@ def main():
127134
check=True,
128135
env=uv_env,
129136
)
137+
if (
138+
not generate_lock_file_only
139+
and not args.skip_installing_pulumi_cli
140+
and platform.system() == "Linux"
141+
and env.lock_file.exists()
142+
and '"pulumi"' in env.lock_file.read_text()
143+
):
144+
if not PULUMI_CLI_INSTALL_SCRIPT.exists():
145+
print(
146+
f"Pulumi CLI install script not found at {PULUMI_CLI_INSTALL_SCRIPT}, skipping Pulumi CLI installation"
147+
)
148+
else:
149+
_ = subprocess.run(
150+
["sh", str(PULUMI_CLI_INSTALL_SCRIPT), str(env.lock_file)],
151+
check=True,
152+
)
130153
elif env.package_manager == PackageManager.PNPM:
131154
pnpm_command = ["pnpm", "install", "--dir", str(env.path)]
132155
if env_check_lock:

.github/actions/install_deps/action.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ inputs:
4444
description: Whether to skip updating the hash when running manual-setup-deps.py
4545
default: true
4646
required: false
47+
skip-installing-pulumi-cli:
48+
type: boolean
49+
description: Whether to skip installing the Pulumi CLI even if the lock file references it
50+
default: false
51+
required: false
4752

4853

4954
runs:
@@ -83,5 +88,5 @@ runs:
8388
- name: Install dependencies
8489
# the funky syntax is github action ternary
8590
if: ${{ inputs.install-deps }}
86-
run: python .devcontainer/manual-setup-deps.py ${{ inputs.python-version == 'notUsing' && '--no-python' || '' }} ${{ inputs.node-version == 'notUsing' && '--no-node' || '' }} ${{ inputs.skip-updating-devcontainer-hash && '--skip-updating-devcontainer-hash' || '' }}
91+
run: python .devcontainer/manual-setup-deps.py ${{ inputs.python-version == 'notUsing' && '--no-python' || '' }} ${{ inputs.node-version == 'notUsing' && '--no-node' || '' }} ${{ inputs.skip-updating-devcontainer-hash && '--skip-updating-devcontainer-hash' || '' }} ${{ inputs.skip-installing-pulumi-cli && '--skip-installing-pulumi-cli' || '' }}
8792
shell: pwsh

.github/workflows/ci.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ env:
1313

1414
permissions:
1515
id-token: write # needed to assume OIDC roles (e.g. for downloading from CodeArtifact)
16+
contents: read # need to explicitly provide this whenever defining permissions because the default value is 'none' for anything not explicitly set when permissions are defined
1617

1718
jobs:
1819
get-values:
@@ -22,6 +23,7 @@ jobs:
2223

2324
check-skip-duplicate:
2425
runs-on: ubuntu-24.04
26+
timeout-minutes: 2
2527
outputs:
2628
should-run: ${{ steps.check.outputs.should-run }}
2729
steps:

.github/workflows/pre-commit.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ jobs:
5050
python-version: ${{ inputs.python-version }}
5151
node-version: ${{ inputs.node-version }}
5252
skip-installing-ssm-plugin-manager: true
53+
skip-installing-pulumi-cli: true
5354

5455
- 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
5556
if: ${{ runner.os != 'Windows' }} # we're just gonna have to YOLO on Windows, because this action doesn't support it yet https://github.com/ben-z/gh-action-mutex/issues/14

AGENTS.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,32 @@ This project is a Copier template used to generate applications that are able to
1717

1818
## Testing
1919

20-
- Always run tests with an explicit path (e.g. uv run pytest tests/unit) — test runners discover all types by default.
20+
- Always run tests with an explicit path (e.g. uv run pytest tests/unit) — test runners discover all types (unit, integration, E2E...) by default.
21+
- When iterating on a single test, run that test in isolation first and confirm it is in the expected state (red or green) before widening to the full suite. Use the most targeted invocation available: a specific test function for Python (e.g. `uv run pytest path/to/test.py::test_name --no-cov`) or a file path and name filter for TypeScript (e.g. `pnpm test-unit -- path/to/test.spec.ts -t "test name" --no-coverage`). Only run the full suite once the target test behaves as expected.
2122
- 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.
2223
- Avoid magic values in comparisons in tests in all languages (like ruff rule PLR2004 specifies)
2324
- Prefer using random values in tests rather than arbitrary ones (e.g. the faker library, uuids, random.randint) when possible. For enums, pick randomly rather than hardcoding one value.
2425
- Avoid loops in tests — assert each item explicitly so failures pinpoint the exact element. When verifying a condition across all items in a collection, collect the violations into a list and assert it's empty (e.g., assert [x for x in items if bad_condition(x)] == []).
25-
- Key `data-testid` selectors off unique IDs (e.g. UUIDs), not human-readable names which may collide or change.
26+
- When a test's final assertion is an absence (e.g., element is `null`, list is empty, modal is closed), include a prior presence assertion confirming the expected state existed before the action that removed it. A test whose only assertion is an absence check can pass vacuously if setup silently failed.
27+
- When asserting a mock or spy was called with specific arguments, always constrain as tightly as possible. In order of preference: (1) assert called exactly once with those args (`assert_called_once_with` in Python, `toHaveBeenCalledExactlyOnceWith` in Vitest/Jest); (2) if multiple calls are expected, assert the total call count and use a positional or last-call assertion (`nthCalledWith`, `lastCalledWith` / `assert_has_calls` with `call_args_list[n]`); (3) plain "called with at any point" (`toHaveBeenCalledWith`, `assert_called_with`) is a last resort only when neither the call count nor the call order can reasonably be constrained.
2628

2729
### Python Testing
2830

29-
- 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)`
31+
- 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)`
3032
- Before writing new mock/spy helpers, check the `tests/unit/` folder for pre-built helpers in files like `fixtures.py` or `*mocks.py`
3133
- 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
3234
- 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.
3335
- When using the faker library, prefer the pytest fixture (provided by the faker library) over instantiating instances of Faker.
36+
- **Choosing between cassettes and mocks:** At the layer that directly wraps an external API or service, strongly prefer VCR cassette-recorded interactions (via pytest-recording/vcrpy) — they capture real HTTP traffic and verify the wire format, catching integration issues that mocks would miss. At layers above that (e.g. business logic, route handlers), mock the wrapper layer instead (e.g. `mocker.patch.object(ThresholdsRepository, ...)`) — there is no value in re-testing the HTTP interaction from higher up.
3437
- **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 <test path> --no-cov`. The default mode is `none` — a missing cassette will cause an error, which is expected until recorded.
3538
- **Never hand-edit syrupy snapshot files.** Snapshots are auto-generated — to create or update them, run `uv run pytest --snapshot-update <test path> --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.
3639
- **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 <test path> --no-cov`. The default mode replays recordings — a missing recording causes an error, which is expected until recorded against a live device.
3740

41+
### Frontend Testing
42+
43+
- Key `data-testid` selectors off unique IDs (e.g. UUIDs), not human-readable names which may collide or change.
44+
- In DOM-based tests, scope queries to the tightest relevant container. Only query `document` or `document.body` directly to find the top-level portal/popup element (e.g. a Reka UI dialog via `[role="dialog"][data-state="open"]`); all further queries should run on that element, not on `document.body` again.
45+
3846
# Agent Implementations & Configurations
3947

4048
## Memory and Rules
@@ -49,7 +57,8 @@ This project is a Copier template used to generate applications that are able to
4957
- For frontend tests, run commands via `pnpm` scripts from `frontend/package.json` — never invoke tools directly (not pnpm exec <tool>, npx <tool>, etc.). ✅ pnpm test-unit ❌ pnpm vitest ... or npx vitest ...
5058
- For linting and type-checking, prefer `pre-commit run <hook-id>` 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`.
5159
- 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.
52-
- 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
60+
- 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 `cd` to change to the directory you want before running the command, avoiding the need 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
61+
- Never use `pnpm --prefix <path>` or `uv --directory <path>` to target a different directory — these flags break the permission allow-list matcher the same way chained `cd &&` commands do. Instead, rely on the working directory already being correct (the cwd persists between Bash tool calls), or issue a plain `cd <path>` as a separate prior tool call to reposition before running the command.
5362
- Never use backslash line continuations in shell commands — always write the full command on a single line. Backslashes break the permission allow-list matcher.
5463
- **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.
5564

0 commit comments

Comments
 (0)