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
69 changes: 69 additions & 0 deletions .claude/skills/finish-branch/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
name: finish-branch
description: Use when wrapping up, finalizing, or merging a feature branch in this repo. Distils the branch's agent-generated plans/specs into a durable Architecture Decision Record when warranted (with human approval), then reminds the user to remove the plans/specs in a cleanup commit before the usual tests/merge/cleanup steps.
---

# Finish branch

Finalize a feature branch in this repo. The distinctive step is **distilling the
branch's agent-generated plans/specs into a durable ADR** when warranted, then
removing those plans/specs in a cleanup commit before merge — see `AGENTS.md`
and `docs/adr/README.md` for the policy. This is advisory: surface it to the
user, don't enforce it.

Do not start until the feature work itself is complete and the user is wrapping
up the branch.

## Checklist

Create a TodoWrite item per step and work them in order.

1. **Confirm scope.** Identify the branch and its diff against `main`
(`git log --oneline main..HEAD`, `git diff main...HEAD --stat`). Confirm with
the user that the feature is done and ready to finalize.

2. **Gather the working artifacts.** Find the agent-generated plans/specs for
this branch (typically under `docs/superpowers/plans/` and
`docs/superpowers/specs/`; they may or may not be committed). Read them with
the commit messages and the diff. You now have the full "what and why."

3. **Decide if an ADR is warranted.** Apply the bar in `docs/adr/README.md`:
write one only if a future maintainer would ask "why is it like this?" and
the code won't answer — an architectural choice, a reversal of a prior
decision, a cross-cutting convention, or a non-obvious trade-off.
**Most branches warrant none.** If none: say so, with a one-line reason, and
skip to step 6.

4. **Draft the ADR.** Copy `docs/adr/template.md` to the next number
`docs/adr/NNNN-short-title.md`. Fill in Context / Decision / Consequences /
Alternatives considered. Keep it short; link to the key commit(s)/PR and to
any superseded ADR. Add a row to the index table in `docs/adr/README.md`.

5. **Get human approval of the ADR.** Show the user the drafted ADR and ask them
to confirm or edit before proceeding. Judgment about what is architecturally
relevant stays with the human. Revise until approved.

6. **Verify green.** Run the checks CI runs and the branch touched:
`python -m pytest`, `flake8 . --max-line-length=127`, and `mypy optimizerapi`.
Report results honestly; do not finalize over failures without the user's
say-so (a known, documented pre-existing failure is the user's call).

7. **Clean up the plans/specs.** If they were committed on the branch, remove
them in a dedicated cleanup commit so they don't land on `main` — their
durable content (if any) is now in the ADR, the rest stays in git history.
Keep them through review; the natural time to remove is just before merge.
**Don't enforce this** — surface it: remind the user and confirm, rather than
blocking the merge if they'd rather keep them.

8. **Finalize.** Commit the ADR (and code, if uncommitted) on the branch, then
open a PR or merge per the user's preference and delete the branch. If the
`superpowers:finishing-a-development-branch` skill is available, use it for
this git mechanics step. End git commit messages with the repo's required
`Co-Authored-By` trailer.

## Notes

- One ADR per branch is a smell — recording nothing is the common, healthy case.
- Don't invent a timeframe or follow-up the branch didn't actually create.
- Respect the repo's commit-signing setup; if it was temporarily disabled for a
history rewrite, restore it before the user signs.
13 changes: 13 additions & 0 deletions .devcontainer-allowlist
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
registry-1.docker.io
auth.docker.io
production.cloudfront.docker.com
ghcr.io
pkg-containers.githubusercontent.com
deb.debian.org
security.debian.org
pypi.org
files.pythonhosted.org
download.pytorch.org
download-r2.pytorch.org
github.com
codeload.github.com
25 changes: 25 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Keep the build context small and reproducible: the image installs deps from
# pyproject.toml via uv, so local virtualenvs, VCS, tests, and docs are noise.
.git
.github
.venv
env
*.egg-info
__pycache__
**/__pycache__
*.pyc
.mypy_cache
.pytest_cache
.ruff_cache
tests
docs
scripts
.claude
.sisyphus
*.md
!README.md
mise.toml
.flake8
pytest.ini
Dockerfile
.dockerignore
5 changes: 5 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[flake8]
max-line-length = 127
# Keep flake8's built-in excludes and skip the local virtualenv and build
# artifacts so `flake8 .` lints only project sources (matches CI).
extend-exclude = .venv,env,build,dist,*.egg-info
22 changes: 11 additions & 11 deletions .github/workflows/github-actions-docker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
-
name: Docker meta
id: meta
uses: docker/metadata-action@v3
uses: docker/metadata-action@v5
with:
# list of Docker images to use as base name for tags
images: |
Expand All @@ -39,30 +39,30 @@ jobs:
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
with:
platforms: all
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v3
-
name: Login to GitHub Container Registry
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
# amd64 only — arm64 was emulated under QEMU and dominated build time.
platforms: linux/amd64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# Reuse layers across runs so the heavy dependency layer is not rebuilt
# when only application code changes.
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
GITHUB_REF_NAME
GITHUB_SHA
30 changes: 15 additions & 15 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

# Installs dependencies, lints, type-checks, and tests using the same
# toolchain as local development (mise-managed Python + uv; see mise.toml).
name: Python application

on:
Expand All @@ -15,22 +14,23 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
uses: actions/setup-python@v2
- uses: actions/checkout@v4
- name: Set up toolchain with mise (Python + uv, matches dev env)
uses: jdx/mise-action@v2
with:
python-version: 3.9
install: true
cache: true
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest
if [ -f requirements-freeze.txt ]; then pip install -r requirements-freeze.txt; fi
# mise.toml provisions Python 3.13 + uv and creates .venv; install the
# project with its dev extras (pytest, flake8, mypy) into that venv.
run: mise exec -- uv pip install -e ".[dev]"
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
mise exec -- flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
mise exec -- flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Type-check with mypy
run: mise exec -- mypy optimizerapi
- name: Test with pytest
run: |
pytest
run: mise exec -- python -m pytest
13 changes: 12 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,15 @@
.vscode
/tmp
version.txt
.env
.env

*.egg-info/
dist/
build/
*.egg
.sisyphus
# Ignore local Claude Code settings, but keep shared skills tracked.
/.claude/*
!/.claude/skills/
/.claude/skills/*
!/.claude/skills/finish-branch/
68 changes: 68 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# AGENTS

- Before large refactors, run `python -m pytest`, `flake8 . --max-line-length=127`, and `mypy optimizerapi` to match CI.
- Consult `README.md` alongside this file for end-user setup and deployment details.
- Dev server runs on port 9090; Swagger UI at `/v1.0/ui/`.
- Follow existing style: double quotes for JSON-like payload keys, snake_case for functions/variables, CapWords for classes.
- For development dependencies: `uv pip install -e ".[dev]"`
- Handle errors explicitly; avoid bare `except:` and log or re-raise with context where appropriate.
- Keep functions small and focused; share logic between API handlers and tests instead of duplicating.
- Keep module-level constants UPPER_SNAKE_CASE; avoid one-letter names except simple indices.
- Lint locally with `flake8 . --max-line-length=127` (matches CI workflow).
- No Cursor or GitHub Copilot instruction files are present; if added later, update this AGENTS file to reference them.
- Prefer pure functions where practical; avoid side effects in import time except for necessary configuration.
- Prefer standard-library imports, then third-party, then local; keep tests mirroring src layout.
- Preserve current behavior around environment variables (e.g. `FLASK_ENV`, `CORS_ORIGIN`, `USE_WORKER`).
- Run a single test by node id, e.g. `python -m pytest tests/test_optimizer.py::test_can_be_run_without_data`.
- Run a single test module via `python -m pytest tests/test_optimizer.py`.
- Run the full test suite with `python -m pytest` (watch mode: `ptw`).
- Run the worker (requires Redis) with `python -m optimizerapi.worker`.
- Start the dev server with `python -m optimizerapi.server` (see README.md).
- Type-check locally with `mypy optimizerapi` (matches CI workflow); see `[tool.mypy]` in `pyproject.toml`.
- Type hints are required on new or changed public functions; keep signatures simple and ensure `mypy optimizerapi` passes.
- Use Python 3.13 (pinned in `mise.toml`); with [mise](https://mise.jdx.dev/) installed, `mise install` provisions Python, `uv`, and `.venv` automatically. Without mise, install Python 3.13 and `uv` manually, then `uv pip install -e .`.
- When adding dependencies, update `pyproject.toml` in the `dependencies` array
(or `dev` in `optional-dependencies` for dev tools) and verify with `git diff`
(see README.md).
- When modifying the API, update `optimizerapi/openapi/specification.yml` and keep handler names consistent.
- Write tests under `tests/` using `pytest` style; use `unittest.mock.patch` for external effects as in `tests/test_optimizer.py`.

## Decisions and finishing a branch

- **ADRs are the only durable design doc on `main`.** Significant decisions live
as Architecture Decision Records under `docs/adr/` (Nygard format); see
`docs/adr/README.md` for the bar and the workflow. Write one only when a future
maintainer would ask "why is it like this?" and the code won't answer. Most
branches need none — one ADR per branch is a smell.
- **Agent-generated plans/specs are branch-only working artifacts.** The
spec/plan files produced while building (e.g. under `docs/superpowers/`) may
stay on the branch and in the PR for review, but are removed in a cleanup
commit before merge so they don't accumulate on `main`. Their durable content,
if any, is distilled into an ADR first.
- **Branch-finalize flow** (advisory — the agent surfaces it, nothing enforces
it): when wrapping up a branch, (1) decide whether the work warrants an ADR and
draft it with human approval, (2) run the CI checks (`python -m pytest`,
`flake8 . --max-line-length=127`, `mypy optimizerapi`), (3) remove the
branch's plans/specs in a cleanup commit, then (4) open the PR / merge. The
Claude Code `finish-branch` skill (`.claude/skills/finish-branch/`) encodes
this; this policy is vendor-neutral and applies under any agent framework.

## Architecture

This is an OpenAPI-first REST API wrapping [ProcessOptimizer](https://github.com/novonordisk-research/ProcessOptimizer) (Bayesian optimization). The API has a single main endpoint: `POST /optimizer`.

**Request flow:** Connexion validates requests against `optimizerapi/openapi/specification.yml`, routes to handler via `operationId`, handler dispatches to optimizer core logic.

Key modules:
- `optimizerapi/server.py` — Flask/Connexion app init, CORS, Waitress (prod) vs Flask dev server
- `optimizerapi/openapi/specification.yml` — OpenAPI 3.0 spec; defines all schemas and wires handlers via `operationId`
- `optimizerapi/optimizer_handler.py` — HTTP handler; optionally routes work through Redis/RQ job queue (`USE_WORKER=true`) with SHA256-based dedup
- `optimizerapi/optimizer.py` — Core logic: runs optimizer, generates plots (base64 PNG or JSON), handles pickled model reuse, multi-objective support
- `optimizerapi/securepickle/` — Fernet encryption for pickled optimizer state (`PICKLE_KEY` env var)
- `optimizerapi/auth.py` — Optional Keycloak OIDC or static API key auth

**Multi-objective:** When `yi` arrays have >1 element, the optimizer runs in multi-objective mode with separate per-objective plots (`objective_1_*`, `objective_2_*`) and pareto front data.

**Pickled model flow:** Client sends `extras.pickled` to skip expensive GP retraining. If unpickling fails (corrupt, wrong key, schema change), falls back to full run with a warning log.

**Plot formats:** `extras.graphFormat` controls output — `"png"` returns base64 PNG, `"json"` returns structured plot data. JSON mode uses `_get_brownie_bee_1d_plot_safe()` which works around a ProcessOptimizer bug with categorical dimensions.
10 changes: 10 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Source of truth for setup, workflow, style, and architecture: @AGENTS.md.

When wrapping up or merging a feature branch, use the `finish-branch` skill
(`.claude/skills/finish-branch/`): distil any durable decision into an ADR under
`docs/adr/`, then clean up the branch's plans/specs before merge. See the
"Decisions and finishing a branch" section of @AGENTS.md.
Loading
Loading