Skip to content

release: bring main up to develop (every harness ticket)#64

Merged
constk merged 35 commits into
mainfrom
develop
Apr 27, 2026
Merged

release: bring main up to develop (every harness ticket)#64
constk merged 35 commits into
mainfrom
develop

Conversation

@constk
Copy link
Copy Markdown
Owner

@constk constk commented Apr 27, 2026

Summary

First release PR — bringing every harness ticket (#1#27 + #28's automated half) from develop onto main.

After merge:

  • branch-protection.yml is visible on the default branch, unblocking gh workflow run branch-protection.yml --ref main.
  • release-drafter starts drafting the v0.1.0 notes from the merged PRs.
  • release.yml is dormant until the first v*.*.* tag.

Verification

  • All CI gates green on develop head (a16fca2).
  • 27/27 issue tickets closed.
  • Branch-protection JSONs already track 15 contexts; Branch-protection contexts sync meta-gate green.

🤖 Generated with Claude Code

constk and others added 30 commits April 26, 2026 16:42
…se) (#29)

Establishes the minimum project metadata so subsequent harness tickets
have a foundation to land against.

- pyproject.toml: name harness-python-react, version 0.1.0,
  requires-python = ">=3.14", license = "MIT" (PEP 639), seed deps
  fastapi + pydantic v2. [tool.uv] package = false because src/ does
  not ship until #17 — this is an application, not a library.
- uv.lock: resolved against CPython 3.14.3 (10 transitive deps).
- LICENSE: MIT, copyright 2026 Constantinos Koutsakis.
- README.md: 2-sentence stub linking to the issue tracker and
  project board; full README ships in #26.

Refs #1
Port the four config blocks from Teller's pyproject.toml. Bump ruff target-version
and mypy python_version to 3.14. Drop duckdb/sse-starlette mypy overrides
(not in template). Add stub package skeleton (src/{api,agent,tools,data,
observability,models,eval}/__init__.py + tests/__init__.py) so import-linter
contracts can resolve before backend tickets land.

Closes #2

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port .gitignore from Teller (Python-template patterns with anchored /lib/, /dist/,
/build/ so frontend subtrees aren't swallowed). Strip Teller-specific lines.
Add .editorconfig (LF, UTF-8, 4-space Python, 2-space JS/TS/YAML/Markdown).
Add .dockerignore excluding .git/, .github/, .claude/, tests/, docs/, eval/,
node_modules/ from the build context.

Closes #5

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ne) (#3) (#32)

Port .pre-commit-config.yaml from Teller. Bump pinned ruff to v0.15.12 (Teller's
v0.11.0 predates py314 target support). Update the JSONC exclude pattern from
Svelte's jsconfig.json to TypeScript's tsconfig*.json since the template uses TS.

Closes #3

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…frontend-check) (#4) (#33)

Port justfile from Teller. Drop Teller-specific banner/comment text. Replace the
DuckDB-specific integration-tests comment with a domain-neutral phrasing. Update
the docker-build tag from teller:dev to harness-python-react:dev. All recipes
use uv run --frozen so lockfile drift is caught loudly.

Closes #4

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…) (#34)

Port Teller's multi-stage Dockerfile, bump base to python:3.14-slim. Builder
materialises the venv via uv sync --frozen --no-dev; runtime copies only the
venv + src/ onto a fresh slim base, runs as non-root user `app`. HEALTHCHECK
hits /api/v1/health (template's API prefix). Drop the data/ COPY — template has
no data dir. UV_PYTHON_DOWNLOADS=never + UV_PYTHON_PREFERENCE=only-system pin
to the system Python so pyvenv.cfg symlinks survive the stage handover.

Closes #6

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port docker-compose.yml from Teller. Switch the frontend service from a static
nginx build (Teller) to a Vite dev server on port 5173 with bind-mounted source
for HMR. Bump OTel env vars from OTEL_ENDPOINT to the standard
OTEL_EXPORTER_OTLP_ENDPOINT/OTEL_EXPORTER_OTLP_PROTOCOL/OTEL_SERVICE_NAME triple
(matches what the OTel SDK reads in #19). Make .env optional via env_file.path
+required:false so first-time `docker compose up` works without a .env file.

Closes #7

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port .github/ISSUE_TEMPLATE/{feature,bug,eval-regression}.md + config.yml,
.github/pull_request_template.md, and .github/CODEOWNERS verbatim from Teller.
Templates are already domain-neutral. CODEOWNERS keeps the solo-owner pattern
(@constk on every path).

Closes #8

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…start) + settings.local.json.example (#15) (#37)

Port the three .claude/hooks/*.py scripts and settings.local.json.example
verbatim from Teller. Drop .svelte from PRETTIER_EXTENSIONS and add .jsx
(template uses React, not Svelte). Strip Teller-specific PR references in
comments. Run ruff format on the result.

.claude/bash-log.txt and .claude/settings.local.json are already in .gitignore
from #5.

Closes #15

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port the six skills from Teller (architect, code-reviewer, devops, frontend,
qa-engineer, technical-writer). Strip Teller- and Tomoro-specific references:
- "Teller" -> "this project"
- frontend skill rewritten for React 19.2 + TS strict + Vite (was Svelte chat UI)
- devops skill: Vite dev server replaces Svelte; CI pipeline expanded to match
  the 7-stage pipeline (lint/format/typecheck/architecture/tests/frontend/security)
- qa-engineer skill: LLM judge model reference replaced with the env-var seam
  (LLM_PROVIDER/LLM_API_KEY) the template ships
- technical-writer skill: drop the take-home REPORT.md; map each docs/*.md to
  its purpose so the agent knows where to put what

All six are still scoped to user-invocable: false (they auto-activate).

Closes #16

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ure) (#9) (#39)

Port .github/workflows/ci.yml from Teller. Bump python-version 3.12 -> 3.14 and
node-version 20 -> 24. Pin all action references to commit SHAs (acceptance
criterion). Make frontend-build / frontend-quality jobs guarded by
`hashFiles('frontend/package.json') != ''` so they skip cleanly until #21.

Coverage gate: pyproject.toml's [tool.coverage.report].fail_under stays at 75
(the eventual target). CI uses --cov-fail-under=0 until #17 + #18 land real
source under src/; once those merge, drop the override and let CI honour the
pyproject default.

Drops the meta-gate jobs (branch-protection-sync, commit-type-sync) — those
land in #10 with the .github/scripts/ they invoke.

Closes #9

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The pinned SHA in #9 (e58605a9...) was the tag-object SHA, not the commit
SHA. GitHub Actions does not auto-dereference annotated tags at workflow
startup, so every run completed in ~0s with conclusion=failure and 0 jobs.

Replace with d4b2f3b6... (the commit v5 points to). Update the bump-recipe
comment to use `gh api .../commits/<tag>` which always returns the commit
SHA whether the tag is lightweight or annotated.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the test-integration job entirely (no integration tests exist yet; will
be re-added with #17+ once a real integration suite ships). Drop the per-step
shell/continue-on-error overrides on the run blocks (defaults are correct).
Drop the job-level defaults: run: working-directory: frontend on
frontend-quality and inline `cd frontend &&` on each step (matches the
frontend-build job style; simpler shape that is known-good).

Add `-o addopts=` to the pytest unit-tests invocation so pyproject's `addopts`
do not pull in coverage by default in this fast-feedback job (the coverage
job is the one that enforces the gate).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
)

Root cause of every "completed/failure with 0 jobs in 0s" run since #9 was
`astral-sh/setup-uv@v5` — even with the commit-SHA dereferenced correctly,
v5's pinned commit no longer satisfies GitHub Actions' validation. v8 (latest
major, commit cec20831...) is the supported form.

Now restores: lint, typecheck, test-unit, coverage, architecture, pre-commit,
frontend-build (guarded by hashFiles('frontend/package.json')), frontend-
quality (same guard). Drops test-integration — empty integration suite makes
pytest exit 5 and there's no integration-marker test to land at this stage;
add it back when a real integration test arrives.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
)

Final piece of #9: with no tests at all, pytest exits with code 5 (no tests
collected) and CI's Unit tests + Coverage jobs fail. test_placeholder makes
the suite collectable; it is replaced by real tests in #17/#18/#19.
Adds the second half of #12 (release-drafter); pr-title.yml itself shipped
in #10's bundle. Port .github/workflows/release-drafter.yml + the categories /
autolabeler / version-resolver config in .github/release-drafter.yml verbatim
from Teller. Pin release-drafter@v6 to commit SHA 6a93d829....

release-drafter is exempt from required-status-checks (workflow runs on push
to main + PR label events; never on every PR). Already in EXEMPT_WORKFLOWS in
check_required_contexts.py.

Closes #12

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… (#53)

Port .github/workflows/release.yml from Teller; bump python-version to 3.14,
update setup-uv pin to v8 commit, checkout pin to v4 latest. Add a docker
login + push pair so the built image lands at
ghcr.io/<owner>/<repo>:<version> AND :latest (acceptance criterion: image
must publish to GHCR). Compute the lowercase repo path via parameter
expansion since GHCR rejects mixed-case path components.

Permissions: contents:write + packages:write. SBOM pinned to
cyclonedx-bom==7.3.0 in a uvx venv so the generator itself doesn't end up
in the SBOM. Sanity-check the JSON before upload.

Closes #13

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…odeQL (#14) (#54)

The JSON specs (.github/branch-protection/{develop,main}.json + README.md)
already shipped in #10. This PR adds the three remaining workflows that
operate against them or alongside them:

- branch-protection.yml — applies the JSON spec to main + develop on schedule
  (Monday 06:00 UTC), workflow_dispatch, and push to main when the spec or
  workflow itself changes. Requires a BRANCH_PROTECTION_TOKEN secret with
  admin:repo scope (default GITHUB_TOKEN cannot edit branch protection on
  the repo it runs in). Step summary diffs before/after each apply.
- artifact-cleanup.yml — weekly artifact pruning (default 7 days, scheduled
  live, manual dry-run by default). Stops the account-wide artifact quota
  from accumulating.
- codeql.yml — placeholder. workflow_dispatch only until the repo is public
  (or gains a GHAS subscription). All `on:` triggers commented in-file with
  the re-activation recipe.

All four workflows are EXEMPT_WORKFLOWS in check_required_contexts.py
(scheduled / dispatch-only / push-to-main-only); the meta-gate stays in
sync at 12 required contexts.

Closes #14

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add src/tools/registry.py: a generic dispatcher mapping tool name -> (input
schema, callable). Each tool's input + output are StrictModel subclasses.
Three-method API matches the issue spec:

- register(name, input_schema) -> decorator
- dispatch(name, raw_input)    -> validates raw dict against input schema,
                                  invokes the tool, returns typed output
- names()                      -> sorted list of registered tool names

UnknownToolError (KeyError subclass) raises on dispatch with a missing
name; Pydantic's ValidationError propagates on bad input (wrong type or
unknown keys via StrictModel's extra="forbid").

Module-global `registry` singleton; `echo_tool` (input/output pair
EchoToolInput/EchoToolOutput) self-registers at module load to demonstrate
the pattern.

Layer hygiene: registry.py imports only from src/models/. Verified by
lint-imports — both contracts still kept.

7 unit tests cover: module-global resolves echo, happy-path dispatch,
unknown-tool error, bad input rejection, unknown-key rejection, duplicate
registration, and registry isolation.

Closes #20

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
constk and others added 5 commits April 27, 2026 18:21
#22, #23) (#59)

Bundles two tickets that share the same frontend touch surface:

#22 — typed SSE client primitive (frontend/src/lib/api/client.ts)
  - Port frontend/src/lib/api/client.js from Teller, rewritten as TS:
    generic over the parsed event shape (`SseEvent` interface), POST + body
    + AbortSignal supported, CRLF normalisation, malformed-chunk recovery
    (warns, doesn't throw).
  - Add typed SseError with optional status; createSession() and
    sendMessage<TEvent>(...) exports.
  - SseError uses an explicit field declaration instead of a parameter
    property — tsconfig's `erasableSyntaxOnly: true` rejects the latter.

#23 — hello page + CSS palette + sample component test
  - Replace the Vite welcome screen with a real App.tsx that fetches
    /api/v1/health and renders `loading | ok | error` states with
    semantic ARIA roles + data-testids.
  - Port the Teller CSS-variable palette to frontend/src/styles/palette.css
    (light + dark via [data-theme='dark']); index.css imports it; App.css
    consumes the tokens (no raw hex).
  - Drop the Vite welcome assets (react.svg, vite.svg, hero.png,
    public/icons.svg).
  - frontend/src/App.test.tsx (Vitest + jsdom + Testing Library): mock
    fetch for the happy path (renders the ok badge with version) and the
    500 path (renders the error message).

Both PRs in one commit since the App page consumes the styles and lib
shape; landing them separately would mean a half-shipped page in between.

Closes #22
Closes #23

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…URITY, ARCHITECTURE + README, CONTRIBUTING, CLAUDE.md, CHANGELOG, TASKS.md (#25, #26) (#61)

Closes #25 + #26 in one PR (the README references docs/* paths and TASKS.md
references everything else; landing them separately would mean a half-broken
docs tree in between).

docs/* (all written for the template, not Teller-flavoured):
- HARNESS.md     — umbrella table mapping every layer to its config file
                   and to the meta-gate(s) that catch drift in it.
- INVARIANTS.md  — five portable rules with numbered slots 6+ for project
                   additions.
- BOUNDARIES.md  — ASCII layer diagram + the import-linter contract spec
                   + how to add a layer cleanly.
- DEVELOPMENT.md — prereqs, first-time setup, dev stack, justfile recipes
                   table, branching diagram, commit-prefix table, CI
                   workflow inventory, agent-hook setup, branch-protection
                   token setup.
- EVAL_HARNESS.md — runner architecture, three tolerance modes, wiring
                    your agent / LLM client, adding a case, opt-in for
                    nightly schedule.
- SECURITY.md    — threat model table + defence-in-depth ASCII map +
                    container hardening notes + explicit out-of-scope list
                    (auth, WAF, rate-limit, secret manager).
- ARCHITECTURE.md — scaffold component diagram, request lifecycle,
                    frontend lifecycle, slots that fill in as the project
                    grows.

Top-level docs:
- README.md       — what ships / quickstart / why-a-harness / docs index /
                    versions table / license.
- CONTRIBUTING.md — branching diagram, commit-prefix table, PR template
                    callouts, "adding a check" recipe.
- CLAUDE.md       — agent project instructions: read-first list, workflow,
                    code conventions, what-not-to-do, skills inventory.
- CHANGELOG.md    — release-drafter seed; first Unreleased entry summarises
                    the harness extraction.
- docs/TASKS.md   — full ticket table with phase grouping + status emoji,
                    matches the GitHub Project board.

Closes #25
Closes #26

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@constk constk merged commit ab89e45 into main Apr 27, 2026
29 of 30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant