From 18f14b3502c307d3a570146fbc431c5e9386b6b8 Mon Sep 17 00:00:00 2001 From: plind-junior <59729252+plind-junior@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:18:07 +0900 Subject: [PATCH 1/5] chore(release): prepare 1.1.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bump the version to 1.1.0 in pyproject.toml, openclaw.plugin.json, and src/vouch/__init__.py, and cut the changelog for the release. the headline is the claude code session auto-capture + session-start recall pair; detect-themes, the settings.json json_merge install strategy, and the pr auto-labeler ride along. two pieces of repair work ship with the prep: the [1.0.0] changelog section is restored — a merge after the release folded its entries back under [Unreleased] and dropped the version header, so the section is rebuilt exactly as it read at the v1.0.0 tag and only post-tag entries moved into [1.1.0]. release.yml is restored — the tag-triggered pypi trusted-publishing workflow was deleted alongside unrelated files in the #95 squash (pypi has 0.1.0 and 1.0.0, so the publisher config is live). it returns unchanged, plus a github-release job that attaches the built sdist and wheel to a github release whose body is the matching changelog section and whose title comes from the annotated tag subject. --- .github/workflows/release.yml | 85 +++++++++++++++++++++++++++++++++++ CHANGELOG.md | 59 ++++++++++++++++-------- openclaw.plugin.json | 2 +- pyproject.toml | 2 +- src/vouch/__init__.py | 2 +- 5 files changed, 128 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..26f6403c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,85 @@ +name: release + +# Publish a version tag (e.g. v1.1.0): build the sdist + wheel, publish to +# PyPI via Trusted Publishing (OIDC — no API token stored), and create the +# GitHub release with the dists attached and the matching CHANGELOG section +# as the body. The release title comes from the annotated tag's subject +# (falls back to the tag name for lightweight tags). +# One-time setup on PyPI: add a trusted publisher for project `vouch-kb` +# pointing at vouchdev/vouch, workflow `release.yml`, environment `pypi`. + +on: + push: + tags: + - "v*" + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - run: python -m pip install --upgrade build + - run: python -m build + - uses: actions/upload-artifact@v7 + with: + name: dist + path: dist/ + + publish: + needs: build + runs-on: ubuntu-latest + environment: pypi + permissions: + id-token: write # required for trusted publishing + steps: + - uses: actions/download-artifact@v7 + with: + name: dist + path: dist/ + - uses: pypa/gh-action-pypi-publish@release/v1 + + github-release: + needs: build + runs-on: ubuntu-latest + permissions: + contents: write # create the release for the tag + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v7 + with: + name: dist + path: dist/ + - name: collect release title and notes + id: notes + run: | + tag="${GITHUB_REF_NAME}" + version="${tag#v}" + awk -v ver="$version" ' + $0 ~ "^## \\[" ver "\\]" { on = 1; next } + on && /^## \[/ { exit } + on { print } + ' CHANGELOG.md > release-notes.md + if ! [ -s release-notes.md ]; then + echo "no CHANGELOG section for ${version}; falling back" >&2 + printf 'See CHANGELOG.md.\n' > release-notes.md + fi + if [ "$(git cat-file -t "$tag" 2>/dev/null)" = "tag" ]; then + title="$(git tag -l --format='%(contents:subject)' "$tag")" + else + title="$tag" + fi + printf 'title=%s\n' "${title:-$tag}" >> "$GITHUB_OUTPUT" + - name: create release + env: + GH_TOKEN: ${{ github.token }} + TITLE: ${{ steps.notes.outputs.title }} + run: | + gh release create "${GITHUB_REF_NAME}" dist/* \ + --title "$TITLE" \ + --notes-file release-notes.md diff --git a/CHANGELOG.md b/CHANGELOG.md index b03d5c95..97fd56de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,15 +6,7 @@ All notable changes to vouch are documented here. Format follows ## [Unreleased] -### Docs -- example KBs now carry their own screenshots: `examples/README.md` and the - `tiny/` + `decision-log/` READMEs embed terminal renders of `vouch status`, - `search`, `show`, `audit`, and a supersession `diff` against the shipped - fixtures, so a reader can see what vouch looks like before installing it. - Images live under `docs/img/examples/` and are generated deterministically - from the fixtures by `docs/img/examples/render.py` (`make - examples-screenshots`); `tests/test_example_screenshots.py` asserts the - committed SVGs stay reproducible (#286). +## [1.1.0] — 2026-07-03 ### Added - auto-capture: claude code sessions are harvested via hooks and filed as a @@ -49,6 +41,44 @@ All notable changes to vouch are documented here. Format follows one-shot propose-all and `--json` for machine-readable output. Configurable via `themes.min_sessions`, `themes.min_claims`, `themes.top_k`, and `themes.enabled` in `config.yaml` (#311). +- dual-solve JSON, review-ui job, and choose responses now include + `changed_files` for each candidate and the kept branch, so desktop and browser + clients can show the resulting files without parsing unified diffs. + +### Changed +- `vouch dual-solve --sandbox` default docker image is now + `vouch/coder:latest` (was `amika/coder:latest`). + +### Fixed +- `vouch pending` (and every bulk `list_*` path) no longer crashes when a + single artifact file is unreadable — a corrupt or mojibake yaml is skipped + with a warning instead of aborting the whole listing. +- all text-mode file i/o under `src/vouch/` now pins `encoding="utf-8"`, so a + non-utf-8 locale (e.g. latin-1) can no longer mangle non-ascii claim text + into raw control bytes that the yaml loader rejects, nor crash on write. + +### Packaging +- restored the tag-triggered `release.yml` workflow that was accidentally + deleted alongside unrelated files in the #95 squash. It publishes to PyPI + via Trusted Publishing (OIDC) exactly as before, and now also creates the + GitHub release for the tag with the built sdist and wheel attached and the + matching CHANGELOG section as the release body. +- the `[1.0.0]` section below was restored: a merge after the release folded + its entries back under `[Unreleased]`, dropping the version header. + +### Docs +- example KBs now carry their own screenshots: `examples/README.md` and the + `tiny/` + `decision-log/` READMEs embed terminal renders of `vouch status`, + `search`, `show`, `audit`, and a supersession `diff` against the shipped + fixtures, so a reader can see what vouch looks like before installing it. + Images live under `docs/img/examples/` and are generated deterministically + from the fixtures by `docs/img/examples/render.py` (`make + examples-screenshots`); `tests/test_example_screenshots.py` asserts the + committed SVGs stay reproducible (#286). + +## [1.0.0] — 2026-06-26 + +### Added - `vouch dual-solve ` — run claude + codex on one github issue in isolated git worktrees, compare the two diffs, keep the branch you pick, and propose the chosen solution's rationale into the KB. A sibling tool to @@ -61,14 +91,11 @@ All notable changes to vouch are documented here. Format follows with elapsed time and diff size) reports progress to stderr while it works. - `vouch dual-solve --sandbox` and `vouch review-ui --dual-solve-sandbox` — run Claude Code and Codex inside a - Docker image (default `vouch/coder:latest`) while leaving git/GitHub commands + Docker image (default `amika/coder:latest`) while leaving git/GitHub commands on the host. The sandbox runner mounts only each candidate worktree plus a temporary copied home containing known Claude/Codex credential files, so agent writes stay in the throwaway dual-solve branches and host credential files are not modified. -- dual-solve JSON, review-ui job, and choose responses now include - `changed_files` for each candidate and the kept branch, so desktop and browser - clients can show the resulting files without parsing unified diffs. - `vouch review-ui --allow-dual-solve` — a browser SPA that runs `dual-solve` on a github issue link, streams progress over the review-ui's websocket, shows both engines' diffs side by side, and lets you pick the winner. Off by default; @@ -123,12 +150,6 @@ All notable changes to vouch are documented here. Format follows KB under `eval/fixture-kb/`, and an `eval` workflow gating retrieval changes (#226). ### Fixed -- `vouch pending` (and every bulk `list_*` path) no longer crashes when a - single artifact file is unreadable — a corrupt or mojibake yaml is skipped - with a warning instead of aborting the whole listing. -- all text-mode file i/o under `src/vouch/` now pins `encoding="utf-8"`, so a - non-utf-8 locale (e.g. latin-1) can no longer mangle non-ascii claim text - into raw control bytes that the yaml loader rejects, nor crash on write. - `parse_since` (the `--since` parser behind `vouch metrics`/`vouch audit`) now raises a clean `MetricsError` for a duration too large to represent (e.g. `--since 1000000000000d`), instead of letting an uncaught `OverflowError` traceback escape — restoring the documented "clean error, not a traceback" contract. - `sync_apply` now loads the sync source exactly once and passes the same `_SyncSource` instance into `sync_check`, closing a TOCTOU window where a bundle replaced on disk between the two `_load_source` calls could cause the validation and write phases to operate on different snapshots. Also eliminates redundant directory walks (KB sources) and triple tarball opens (bundle sources). Fixes #217. - `vault_to_kb` now passes `slug_hint=page_id` to `propose_page` so vault edit proposals target the existing page id from frontmatter instead of a slugified copy of the title (fixes #219). diff --git a/openclaw.plugin.json b/openclaw.plugin.json index cc4aff03..a79fa472 100644 --- a/openclaw.plugin.json +++ b/openclaw.plugin.json @@ -1,6 +1,6 @@ { "name": "vouch", - "version": "1.0.0", + "version": "1.1.0", "description": "Git-native, review-gated knowledge base for LLM agents. MCP server + JSONL tool server + CLI.", "family": "bundle-plugin", "homepage": "https://github.com/vouchdev/vouch", diff --git a/pyproject.toml b/pyproject.toml index 516cb65d..c51daa92 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "vouch" -version = "1.0.0" +version = "1.1.0" description = "Git-native, review-gated knowledge base for LLM agents. MCP server + CLI." readme = "README.md" requires-python = ">=3.11" diff --git a/src/vouch/__init__.py b/src/vouch/__init__.py index 4e2a86c7..b905a610 100644 --- a/src/vouch/__init__.py +++ b/src/vouch/__init__.py @@ -1,3 +1,3 @@ """vouch — git-native, review-gated knowledge base for LLM agents.""" -__version__ = "1.0.0" +__version__ = "1.1.0" From e725383525da7e5e104ec913dc7afe74646045ea Mon Sep 17 00:00:00 2001 From: plind-junior <59729252+plind-junior@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:25:15 +0900 Subject: [PATCH 2/5] fix(packaging): restore vouch-kb distribution name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit the #95 squash that deleted release.yml also reverted the pyproject name from vouch-kb to vouch. pypi rejects uploads under vouch (the name belongs to an unrelated project) and the trusted publisher is registered for vouch-kb — the v1.1.0 publish job failed 403 on exactly this. the installed command is still vouch. also fetch the real tag object in the github-release job before reading its subject: checkout peels the tag to its commit, so the annotated-tag title always fell back to the bare tag name. --- .github/workflows/release.yml | 3 +++ CHANGELOG.md | 4 ++++ pyproject.toml | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 26f6403c..6abaebd3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -60,6 +60,9 @@ jobs: run: | tag="${GITHUB_REF_NAME}" version="${tag#v}" + # checkout leaves the tag peeled to its commit; fetch the real tag + # object so an annotated tag's subject can become the title. + git fetch --force --quiet origin "refs/tags/$tag:refs/tags/$tag" || true awk -v ver="$version" ' $0 ~ "^## \\[" ver "\\]" { on = 1; next } on && /^## \[/ { exit } diff --git a/CHANGELOG.md b/CHANGELOG.md index 97fd56de..c5f31967 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,10 @@ All notable changes to vouch are documented here. Format follows via Trusted Publishing (OIDC) exactly as before, and now also creates the GitHub release for the tag with the built sdist and wheel attached and the matching CHANGELOG section as the release body. +- restored the `vouch-kb` distribution name in `pyproject.toml` — the same + #95 squash had reverted it to `vouch`, which PyPI rejects (the name belongs + to an unrelated project, and the trusted publisher is registered for + `vouch-kb`). The installed command is still `vouch`. - the `[1.0.0]` section below was restored: a merge after the release folded its entries back under `[Unreleased]`, dropping the version header. diff --git a/pyproject.toml b/pyproject.toml index c51daa92..f50e1506 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "vouch" +name = "vouch-kb" version = "1.1.0" description = "Git-native, review-gated knowledge base for LLM agents. MCP server + CLI." readme = "README.md" From 5f58c69cd2adcf7eeb5c59e43ed22fb7078e0828 Mon Sep 17 00:00:00 2001 From: plind-junior <59729252+plind-junior@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:27:22 +0900 Subject: [PATCH 3/5] ci(release): make the github-release job idempotent delete any existing release for the tag before recreating it, so a workflow re-run or a corrected tag replaces the stale release with fresh assets instead of failing on "already exists". --- .github/workflows/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6abaebd3..5f5d42e3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -83,6 +83,9 @@ jobs: GH_TOKEN: ${{ github.token }} TITLE: ${{ steps.notes.outputs.title }} run: | + # idempotent: a re-run or a moved tag replaces the stale release + # instead of failing on "already exists". + gh release delete "${GITHUB_REF_NAME}" --yes --repo "$GITHUB_REPOSITORY" || true gh release create "${GITHUB_REF_NAME}" dist/* \ --title "$TITLE" \ --notes-file release-notes.md From d80b1e22db514e61b44c54a3539605a2a3046d85 Mon Sep 17 00:00:00 2001 From: plind-junior <59729252+plind-junior@users.noreply.github.com> Date: Fri, 3 Jul 2026 17:24:12 +0900 Subject: [PATCH 4/5] feat(packaging): publish a container image to ghcr.io/vouchdev/vouch every release tag now also builds and pushes a linux/amd64 + linux/arm64 image tagged X.Y.Z, X.Y, and latest, gh-pages style alongside the pypi dists. the new repo-root Dockerfile is the canonical general-purpose container: entrypoint is the vouch cli, default command is the stdio mcp server, and the project root bind-mounts at /data where the kb is discovered from cwd exactly as on a host checkout. installs the web extra; embeddings stay out. the image user is uid 1000 with a home dir, so files created through the /data bind mount stay owned by the default host user (uid 10001 could not even stat a host-owned mount). verified locally: build, vouch init + status against a bind mount, and an mcp initialize handshake over stdio. the pypi publish step gains skip-existing so a re-cut tag republishes cleanly instead of failing on the immutable existing version. adapters/http-tunnel/Dockerfile remains the http-only deployment reference. --- .dockerignore | 18 ++++++++++++ .github/workflows/release.yml | 46 +++++++++++++++++++++++++++--- CHANGELOG.md | 5 ++++ Dockerfile | 53 +++++++++++++++++++++++++++++++++++ README.md | 13 +++++++++ 5 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..a3ec3b3c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,18 @@ +# Keep the build context to what the Dockerfile actually copies +# (pyproject.toml, README.md, src/) plus room for local experiments. +.git +.venv +.vouch +.claude +desktop +docs +eval +examples +web +tests +benchmarks +adapters +skills +proposals +**/__pycache__ +**/*.py[cod] diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5f5d42e3..482b5eb2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,12 +1,14 @@ name: release # Publish a version tag (e.g. v1.1.0): build the sdist + wheel, publish to -# PyPI via Trusted Publishing (OIDC — no API token stored), and create the -# GitHub release with the dists attached and the matching CHANGELOG section -# as the body. The release title comes from the annotated tag's subject -# (falls back to the tag name for lightweight tags). +# PyPI via Trusted Publishing (OIDC — no API token stored), push the +# container image to ghcr.io/vouchdev/vouch, and create the GitHub release +# with the dists attached and the matching CHANGELOG section as the body. +# The release title comes from the annotated tag's subject (falls back to +# the tag name for lightweight tags). # One-time setup on PyPI: add a trusted publisher for project `vouch-kb` # pointing at vouchdev/vouch, workflow `release.yml`, environment `pypi`. +# Every job is idempotent, so a re-cut tag republishes cleanly. on: push: @@ -43,6 +45,42 @@ jobs: name: dist path: dist/ - uses: pypa/gh-action-pypi-publish@release/v1 + with: + # tolerate re-runs and re-cut tags — pypi versions are immutable, + # so an already-published dist is skipped instead of failing 400. + skip-existing: true + + container: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write # push ghcr.io/vouchdev/vouch + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-qemu-action@v3 + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ github.token }} + - uses: docker/metadata-action@v5 + id: meta + with: + images: ghcr.io/${{ github.repository }} + # v1.1.0 -> 1.1.0, 1.1, latest (latest is automatic for semver) + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + - uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max github-release: needs: build diff --git a/CHANGELOG.md b/CHANGELOG.md index c5f31967..523eb817 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,11 @@ All notable changes to vouch are documented here. Format follows #95 squash had reverted it to `vouch`, which PyPI rejects (the name belongs to an unrelated project, and the trusted publisher is registered for `vouch-kb`). The installed command is still `vouch`. +- container image: every release now also pushes `ghcr.io/vouchdev/vouch` + (linux/amd64 + linux/arm64, tagged `X.Y.Z`, `X.Y`, and `latest`). The + entrypoint is the `vouch` CLI with the stdio MCP server as the default + command; bind-mount the project root at `/data`. Built from the new + repo-root `Dockerfile`; installs the `web` extra, leaves embeddings out. - the `[1.0.0]` section below was restored: a merge after the release folded its entries back under `[Unreleased]`, dropping the version header. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..9643f325 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,53 @@ +# The canonical vouch container, published to ghcr.io/vouchdev/vouch on every +# release tag by .github/workflows/release.yml. General-purpose: the +# entrypoint is the `vouch` CLI itself and the default command is the stdio +# MCP server, so the same image serves MCP hosts, the HTTP transport, and +# one-off CLI calls. +# +# MCP (stdio, the canonical surface — note -i): +# docker run -i --rm -v "$PWD:/data" ghcr.io/vouchdev/vouch +# HTTP transport (refuses a public bind without a bearer token): +# docker run --rm -p 8731:8731 -v "$PWD:/data" -e VOUCH_HTTP_TOKEN=... \ +# ghcr.io/vouchdev/vouch serve --transport http --host 0.0.0.0 --allow-public +# CLI: +# docker run --rm -v "$PWD:/data" ghcr.io/vouchdev/vouch status +# +# Bind-mount the project root (the directory containing .vouch/) at /data; +# the KB is discovered from the working directory exactly as on a host +# checkout. adapters/http-tunnel/Dockerfile remains the opinionated +# HTTP-only deployment reference. + +FROM python:3.13-slim + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + LANG=C.UTF-8 + +LABEL org.opencontainers.image.source="https://github.com/vouchdev/vouch" \ + org.opencontainers.image.description="Git-native, review-gated knowledge base for LLM agents. MCP server + CLI." \ + org.opencontainers.image.licenses="MIT" + +WORKDIR /app + +# Install from this checkout so local dev images never need a PyPI release +# in the loop. The web extra pulls in the review-ui / fastapi surface; +# embeddings stay out (torch does not belong in the default image). +COPY pyproject.toml README.md ./ +COPY src ./src +RUN pip install --no-cache-dir '.[web]' + +# /data is the KB volume mount point: the host's project root, containing +# .vouch/, is served exactly as-is. +VOLUME ["/data"] +WORKDIR /data + +# uid 1000 matches the default first user on Linux hosts, so files created +# through the /data bind mount stay owned by the host user. Override with +# `docker run --user "$(id -u):$(id -g)"` where that assumption is wrong. +RUN useradd --create-home --uid 1000 vouch && chown vouch:vouch /data +USER vouch + +EXPOSE 8731 + +ENTRYPOINT ["vouch"] +CMD ["serve"] diff --git a/README.md b/README.md index 9cd46f44..790a79d8 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,19 @@ Claude Code install to point you at the next step (`vouch install-mcp claude-code`). Inspect it first if you'd like — it's [`install.sh`](install.sh) at the repo root. +Or skip the install entirely and run the released container image +([`ghcr.io/vouchdev/vouch`](https://github.com/vouchdev/vouch/pkgs/container/vouch)), +bind-mounting the project root (the directory containing `.vouch/`) at +`/data`: + +```bash +# stdio MCP server (the canonical surface — note -i) +docker run -i --rm -v "$PWD:/data" ghcr.io/vouchdev/vouch:latest + +# any CLI command +docker run --rm -v "$PWD:/data" ghcr.io/vouchdev/vouch:latest status +``` + ## Running the tests From a clone with the dev extras installed (`pip install -e '.[dev]'`): From 8ea8e85443bf5b8ce5652632abf773cdee838c5e Mon Sep 17 00:00:00 2001 From: plind-junior <59729252+plind-junior@users.noreply.github.com> Date: Sat, 4 Jul 2026 08:58:40 +0900 Subject: [PATCH 5/5] docs(readme): add x social link badge link the @vouch_dev x account from the badge row so the project's social presence is discoverable from the readme. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 790a79d8..39a26707 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ PyPI Python versions MIT licensed + Follow @vouch_dev on X

> Agents should not start every session with amnesia — but they shouldn't get to write whatever they want either.