Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion template/.devcontainer/devcontainer.json.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,11 @@
// Mount the parent as /workspaces so we can pip install peers as editable
"workspaceMount": "source=${localWorkspaceFolder}/..,target=/workspaces,type=bind",{% if add_claude %}
"postCreateCommand": ".devcontainer/postCreate.sh",
"postStartCommand": ".devcontainer/postStart.sh"{% else %}
"postStartCommand": ".devcontainer/postStart.sh",
// VS Code's Dev Containers extension re-injects its credential bridge
// when the editor attaches — after postStart has already run. Re-run
// the cleanup at attach so the leak is closed before any git operation.
"postAttachCommand": ".devcontainer/postStart.sh"{% else %}
// After the container is created, recreate the venv then make pre-commit first run faster
"postCreateCommand": "uv venv --clear && uv sync && pre-commit install --install-hooks"{% endif %}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
#!/bin/bash
set -euo pipefail

# Wipe any credential helpers and SSH URL rewrites injected by VS Code's
# Dev Containers extension when it copies the host gitconfig. An empty-string
# value resets the helper list so only an explicit PAT via `just gh-auth`
# can authenticate to remotes.
git config --global credential.helper ''
git config --global --unset-all url.ssh://git@github.com/.insteadOf 2>/dev/null || true
# Wipe any credential helpers and SSH URL rewrites that VS Code's Dev
# Containers extension injects when it copies the host gitconfig and
# spawns its own credential bridge. We need --unset-all (not =''),
# because VS Code stores the helper as a single multi-valued line that
# `git config <key> <value>` only replaces if there is a single value.
# IMPORTANT: VS Code writes its credential.helper to /etc/gitconfig
# (system scope), not ~/.gitconfig — so the system scope must also be
# cleared, otherwise the helper still runs.
for scope in --system --global; do
git config $scope --unset-all credential.helper 2>/dev/null || true
git config $scope --unset-all credential.https://github.com.helper 2>/dev/null || true
{%- if install_glab %}
git config $scope --unset-all credential.https://gitlab.diamond.ac.uk.helper 2>/dev/null || true
{%- endif %}
git config $scope --unset-all url.ssh://git@github.com/.insteadOf 2>/dev/null || true
done

# VS Code drops a Node-based credential bridge in /tmp that talks back
# to the host over a named pipe — even with VSCODE_GIT_IPC_HANDLE blank
# it can still surface host PATs. Remove it so any stale `credential.helper`
# entries cannot fall through to it.
rm -f /tmp/vscode-remote-containers-*.js

# Force all SSH-style remotes to use HTTPS so the gh/glab credential helpers
# handle auth. This keeps the container SSH-key-free (Claude stays sandboxed)
Expand All @@ -17,9 +33,22 @@ git config --global url."https://gitlab.diamond.ac.uk/".insteadOf "git@gitlab.di
{%- endif %}

{% if install_gh -%}
# Pin per-host helper to the in-container gh path. The host gitconfig may
# reference /usr/local/bin/gh which doesn't exist here (apt installs to
# /usr/bin/gh); without this, git falls through to the next helper.
if command -v gh >/dev/null; then
git config --global credential.https://github.com.helper "!$(command -v gh) auth git-credential"
fi

# If gh CLI has cached credentials (survive container rebuild), re-register
# its git credential helper so HTTPS remotes authenticate automatically.
if gh auth status &>/dev/null; then
gh auth setup-git
fi
{%- endif %}
{% if install_glab %}
# Pin per-host helper to the in-container glab path.
if command -v glab >/dev/null; then
git config --global credential.https://gitlab.diamond.ac.uk.helper "!$(command -v glab) auth git-credential"
fi
{%- endif %}
4 changes: 4 additions & 0 deletions template/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,7 @@ lockfiles/

# ruff cache
.ruff_cache/

# Claude Code local state (commit settings.json, commands, skills, hooks)
.claude/settings.local.json
.claude/scheduled_tasks.lock
6 changes: 4 additions & 2 deletions template/Dockerfile.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ RUN apt-get update -y && apt-get install -y --no-install-recommends \
graphviz \
&& apt-get dist-clean{% if add_claude %}

# Node is required by Claude Code's hook runtime
# Node is required by Claude Code's hook runtime; just powers the
# container's claude/gh-auth/glab-auth recipes in justfile.
RUN apt-get update -y && apt-get install -y --no-install-recommends \
nodejs \
just \
&& apt-get dist-clean{% endif %}{% if install_gh %}

# GitHub CLI — used by Claude to authenticate to github.com via PAT
Expand All @@ -23,7 +25,7 @@ RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | \

# GitLab CLI — used by Claude to authenticate to gitlab instances via PAT.
# No apt repo, so install from the upstream release tarball.
ARG GLAB_VERSION=1.92.1
ARG GLAB_VERSION=1.93.0
RUN curl -fsSL "https://gitlab.com/gitlab-org/cli/-/releases/v${GLAB_VERSION}/downloads/glab_${GLAB_VERSION}_linux_amd64.tar.gz" \
| tar -xz -C /tmp bin/glab && \
install -m 0755 /tmp/bin/glab /usr/local/bin/glab && \
Expand Down
15 changes: 8 additions & 7 deletions template/README.md.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@
This is where you should write a short paragraph that describes what your module does,
how it does it, and why people should use it.

{# #}What | Where
{# #}:---: | :---:
{# #}Source | <{{repo_url}}>
{% if pypi %}PyPI | `pip install {{distribution_name}}`
{% endif %}{% if docker %}Docker | `docker run ghcr.io/{{github_org | lower}}/{{repo_name}}:latest`
{% endif %}{% if sphinx %}Documentation | <{{docs_url}}>
{% endif %}Releases | <{{repo_url}}/releases>
{# #}What | Where
{# #}:---: | :---:
{# #}Source | <{{repo_url}}>
{% if pypi %}PyPI | `pip install {{distribution_name}}`
{% endif %}{% if docker %}Docker | `docker run ghcr.io/{{github_org | lower}}/{{repo_name}}:latest`
{% endif %}{% if sphinx %}Documentation | <{{docs_url}}>
{% endif %}{% if add_claude %}Claude sandbox | [README-CLAUDE.md](./README-CLAUDE.md)
{% endif %}Releases | <{{repo_url}}/releases>

This is where you should put some images or code snippets that illustrate
some relevant examples. If it is a library then you might put some
Expand Down
43 changes: 43 additions & 0 deletions template/{% if add_claude %}.claude{% endif %}/commands/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
description: Save current task state to auto-memory, then promote reusable lessons to skills and trim memory.
---

# Memo

Save a snapshot of current work to persistent memory, then clean up.

## Step 1 — Save current state

Write a concise summary of in-progress or recently completed work to the
auto-memory `MEMORY.md` for this project. Include:

- What was done (feature, bug, refactor, area of code)
- Current status (completed, blocked, in-progress)
- Key decisions or outcomes worth remembering across conversations

Do not duplicate information already in skills, CLAUDE.md, or README-CLAUDE.md.

## Step 2 — Promote to skills

Review the memory file for items that represent **reusable patterns or
lessons** — things that would help future sessions on this project. For
each such item:

1. Identify which skill file it belongs in (or create a new one under
`.claude/skills/<name>/SKILL.md`).
2. Add it to the appropriate skill.
3. Remove it from memory (it now lives in the skill).

Examples of promotable items:
- A non-obvious convention specific to this project
- A "foot-gun" pattern worth warning future-you about
- A reusable recipe (test invocation, deploy command, debugging trick)

## Step 3 — Trim memory

Remove from memory anything that is:
- Already captured in skills, CLAUDE.md, or README-CLAUDE.md
- Too specific to a single completed task to be useful again
- Stale or superseded by later work

Keep memory concise — ideally under 30 lines.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/bash
# UserPromptSubmit hook: verify the Claude sandbox is intact before
# executing any prompt. Exit code 2 blocks the prompt and shows the
# message to the user. See README-CLAUDE.md for the full sandbox model.

fail() { echo "BLOCKED: $1" >&2; exit 2; }

# Are we in the devcontainer at all?
[ -n "${IN_DEVCONTAINER:-}" ] || \
fail "not in the devcontainer (IN_DEVCONTAINER unset). Reopen the project in the devcontainer."

# Host SSH agent must not be reachable.
[ -z "${SSH_AUTH_SOCK:-}" ] || \
fail "SSH_AUTH_SOCK is set ($SSH_AUTH_SOCK) — host SSH agent is reachable."

# VS Code git credential bridge must be silenced.
[ -z "${VSCODE_GIT_IPC_HANDLE:-}" ] || \
fail "VSCODE_GIT_IPC_HANDLE is set — VS Code credential bridge is reachable."
[ -z "${GIT_ASKPASS:-}" ] || \
fail "GIT_ASKPASS is set — VS Code askpass is injected."

# The /tmp credential helper script VS Code drops in must have been removed.
if compgen -G '/tmp/vscode-remote-containers-*.js' >/dev/null; then
fail "/tmp/vscode-remote-containers-*.js bridge present — re-run .devcontainer/postStart.sh."
fi

# system-scope credential.helper is where VS Code injects; if anything
# is set there git will use it before our per-host helpers.
if git config --system --get credential.helper >/dev/null 2>&1; then
fail "system credential.helper is still set — re-run .devcontainer/postStart.sh."
fi

exit 0
38 changes: 38 additions & 0 deletions template/{% if add_claude %}.claude{% endif %}/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"permissions": {
"allow": [
"Edit(/workspaces/**)",
"Write(/workspaces/**)",
"Read(/workspaces/**)",
"Bash(*)"
],
"deny": [
"Bash(git push --force *)",
"Bash(git reset --hard*)",
"Bash(ssh *)",
"Bash(ssh-agent *)",
"Bash(*ssh-agent*)",
"Bash(scp *)",
"Bash(rsync *)",
"Bash(sftp *)",
"Bash(telnet *)",
"Bash(mail *)",
"Bash(sendmail *)"
],
"additionalDirectories": [
"/workspaces/**"
]
},
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": ".claude/hooks/sandbox-check.sh"
}
]
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
name: copier-derived
description: This project was generated from python-copier-template. Use when editing devcontainer / Dockerfile / .github / pre-commit / justfile / .gitleaks / renovate config, or when the user asks about updating from the template, resolving copier conflicts, or why a config looks the way it does.
---

# Copier-template-derived project

This project was generated from
[python-copier-template](https://github.com/diamondlightsource/python-copier-template).
The template is recorded in `.copier-answers.yml`:

```bash
grep _src_path .copier-answers.yml # template source
grep _commit .copier-answers.yml # version applied
```

## Template-managed files

`copier update` overwrites these from the template. Local edits will
either merge cleanly (good) or produce `.rej` / inline conflicts.
**Prefer editing the upstream template** for any change that should
apply to all projects — otherwise the next update reverts it.

- `.devcontainer/**`
- `Dockerfile`
- `.github/workflows/*.yml`, `.github/CONTRIBUTING.md`,
`.github/ISSUE_TEMPLATE/`, `.github/PULL_REQUEST_TEMPLATE/`
- `.pre-commit-config.yaml`, `.gitleaks.toml`, `renovate.json`
- `justfile`
- `pyproject.toml` — top-level metadata, build-system, ruff/pyright/mypy
config, tox config (project deps and scripts are project-owned)
- `tests/conftest.py`, `tests/test_cli.py`
- `CLAUDE.md`, `README-CLAUDE.md`, `.claude/**`

## Project-owned files

Edit freely; never overwritten by `copier update`:

- `src/<package>/**`
- New tests under `tests/` (other than the seeded `test_cli.py`)
- `README.md` (rendered once with placeholders, then yours)
- `.copier-answers.yml` answers (only `_commit` / `_src_path` are bumped
by `copier update`)

## When the user asks to change a template-managed file

1. Make the requested change in this project so it works now.
2. **Tell the user** the file is template-managed, and offer to also
update the upstream template if they have it checked out (commonly
at `/workspaces/python-copier-template`). Phrase as a choice — they
may want a project-only patch.
3. If both edits are made, the project edit can be reverted on the
next `copier update` once the template change reaches a release.

## Running `copier update`

The user runs this themselves (it touches many files); only run it
yourself if explicitly asked. Always pass `--trust`. After update,
resolve any conflicts (look for `<<<<<<<` markers and `.rej` files)
before committing.
65 changes: 65 additions & 0 deletions template/{% if add_claude %}CLAUDE.md{% endif %}.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# CLAUDE.md

Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.

**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.

## 1. Think Before Coding

**Don't assume. Don't hide confusion. Surface tradeoffs.**

Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them - don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.

## 2. Simplicity First

**Minimum code that solves the problem. Nothing speculative.**

- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.

Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.

## 3. Surgical Changes

**Touch only what you must. Clean up only your own mess.**

When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.

When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.

The test: Every changed line should trace directly to the user's request.

## 4. Goal-Driven Execution

**Define success criteria. Loop until verified.**

Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"

For multi-step tasks, state a brief plan:
```
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
```

Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.

---

**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
Loading
Loading