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
594 changes: 594 additions & 0 deletions .github/workflows/release.yml

Large diffs are not rendered by default.

52 changes: 41 additions & 11 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,62 +1,92 @@
---
# yamllint disable rule:line-length rule:truthy
name: test

on:
push:
pull_request:

permissions:
contents: read

jobs:
unittest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v8.2.0
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
python-version: "3.11"
enable-cache: true
- uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
with:
just-version: "1.58.0"
- name: Run unittest suite
run: make test
run: just test

package:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
python-version: "3.11"
enable-cache: true
- uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
with:
just-version: "1.58.0"
- name: Build and validate package metadata
run: |
just build
just check-dist

# Amber admission gates on the exact deployed Hermes fork contract. Keep the
# immutable revision aligned with infra's candidate image/source receipt.
hermes-deployed-context-engine-contract:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
repository: NousResearch/hermes-agent
ref: f80f453ae0679347e38abc917c7f94f717bf96c5
path: .hermes-agent-deployed
- uses: astral-sh/setup-uv@v8.2.0
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
python-version: "3.11"
enable-cache: true
- uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
with:
just-version: "1.58.0"
- name: Run context-engine contract against deployed Hermes revision
env:
HERMES_AGENT_PATH: ${{ github.workspace }}/.hermes-agent-deployed
run: |
test "$(git -C "$HERMES_AGENT_PATH" rev-parse HEAD)" = \
"f80f453ae0679347e38abc917c7f94f717bf96c5"
uv run python -m unittest tests.test_context_engine_contract -v
just test-context-engine-contract

# Track forward drift against upstream main without making Amber depend on
# APIs that are newer than its exact deployed host.
hermes-contract:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
repository: NousResearch/hermes-agent
path: .hermes-agent
- uses: astral-sh/setup-uv@v8.2.0
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
python-version: "3.11"
enable-cache: true
- uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
with:
just-version: "1.58.0"
- name: Run hermes contract tests against upstream main
env:
HERMES_AGENT_PATH: ${{ github.workspace }}/.hermes-agent
run: |
git -C "$HERMES_AGENT_PATH" rev-parse HEAD
uv run python -m unittest tests.test_hermes_contract -v
just test-contract
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ plugin.
and recovery dispatch on `get_tool_schemas` / `handle_tool_call`; do not
duplicate native engine tools through `@tool`.
- Redact secret-looking values in logs and avoid logging full untrusted payloads.
- Use `uv` and the Makefile for local development:
`make install`, `make test`, `make test-one T=tests.test_kit.SchemaConventionTests`,
and `make build`.
- Use `uv` and the `justfile` for local development:
`just install`, `just test`,
`just test-one tests.test_kit.SchemaConventionTests`, and `just build`.

## Release Notes

Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# CHANGELOG

<!-- version list -->

Release notes are generated from Conventional Commits by Python Semantic Release.
34 changes: 0 additions & 34 deletions Makefile

This file was deleted.

103 changes: 85 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
> Lifecycle helpers for [hermes-agent](https://github.com/NousResearch/hermes-agent) plugins — convention-correct commands, tools, middleware, hooks, skills, validation, and safe logging, baked in.

[![test](https://github.com/offendingcommit/hermes-plugin-kit/actions/workflows/test.yml/badge.svg)](https://github.com/offendingcommit/hermes-plugin-kit/actions/workflows/test.yml)
[![PyPI](https://img.shields.io/pypi/v/hermes-plugin-kit)](https://pypi.org/project/hermes-plugin-kit/)
![python](https://img.shields.io/badge/python-3.11%2B-blue)

`hermes-plugin-kit` is a tiny, dependency-free helper for authoring plugins for
Expand Down Expand Up @@ -80,23 +81,21 @@ and adds nothing to your runtime footprint — pure standard library.

## Install

The package is consumed straight from Git (works great with [uv](https://docs.astral.sh/uv/)):
Install a published release from PyPI with [uv](https://docs.astral.sh/uv/) or pip:

```bash
uv add git+https://github.com/offendingcommit/hermes-plugin-kit.git
# or
pip install git+https://github.com/offendingcommit/hermes-plugin-kit.git
uv add "hermes-plugin-kit>=0.7,<1"
# or: pip install "hermes-plugin-kit>=0.7,<1"
```

With uv, pin it to an immutable commit in your plugin's `pyproject.toml`.
Profiles that load several plugins into one Python environment must keep every
plugin on the same kit revision:
Consumers declare the narrowest truthful compatibility range in
`pyproject.toml`; adopting a newer kit API and raising that lower bound are one
change. Repository locks remain exact for reproducible local tests. Fleet
deployment independently selects one qualified wheel filename and SHA-256 for
every co-loaded plugin, so a movable branch is never a deployment identity.

```toml
dependencies = ["hermes-plugin-kit"]

[tool.uv.sources]
hermes-plugin-kit = { git = "https://github.com/offendingcommit/hermes-plugin-kit.git", rev = "<commit-sha>" }
dependencies = ["hermes-plugin-kit>=0.7,<1"]
```

## Usage
Expand Down Expand Up @@ -717,17 +716,85 @@ so the linked directory is self-contained.

## Development

Uses [uv](https://docs.astral.sh/uv/). Install it with `brew install uv` (macOS) or
`curl -LsSf https://astral.sh/uv/install.sh | sh`.
Uses [uv](https://docs.astral.sh/uv/) and
[Just](https://github.com/casey/just). On macOS, install both with
`brew install uv just`; otherwise install them using their platform-specific
instructions.

```bash
make install # uv sync — create/sync the dev environment
make test # uv run python -m unittest discover -s tests
make test-one T=tests.test_kit.SchemaConventionTests
make build # uv build — wheel + sdist
just install # uv sync — create/sync the dev environment
just test # uv run python -m unittest discover -s tests
just test-one tests.test_kit.SchemaConventionTests
just test-release # release intent, artifact identity, and workflow contracts
just test-contract # real upstream Hermes contract
just build # uv build — wheel + sdist
just check-dist # validate wheel/sdist metadata with twine
```

CI runs `make test` on `actions/checkout@v6` + `astral-sh/setup-uv@v8.2.0` (Python 3.11).
CI pins every Action to an immutable commit and runs the unit, package metadata,
and real-Hermes contract lanes with read-only repository permissions.

## Releases

Merges to `main` use Python Semantic Release 10.6.x and Conventional Commits:
`fix` produces a patch, `feat` produces a minor, and `!` or a
`BREAKING CHANGE:` footer produces a major. Documentation, test, CI, and chore
commits do not release by themselves; an invalid commit in release history
fails closed. Major releases are published but their receipt is always
`manual_migration_required`, never an automatic promotion candidate.

The workflow first creates the version/CHANGELOG commit and tag locally, then
bundles that exact final source before executing tests. Fresh, separate jobs
restore the bundle for unit/public tests, the mutable upstream-Hermes contract,
and the one artifact build. The build job is gated on both test jobs and never
checks out or executes Hermes code. Every lane verifies the release SHA, tag,
parent, and clean tracked source before continuing.

The build produces the wheel and sdist once, validates their metadata, and
writes a prepublication manifest containing their filenames, sizes, and SHA-256
values. Only then may the protected source-promotion job atomically push that
tested commit and tag. A separate `pypi` environment publishes the uploaded
artifacts through OIDC Trusted Publishing; no password or API token is used.
After publication, the workflow downloads and hashes the registry files and
turns the manifest into the final receipt by adding each verified direct
`https://files.pythonhosted.org/` URL. The immutable GitHub Release uploads that
final receipt only after PyPI verification. A failed publish is retried from
the retained workflow artifact and must not rebuild it.

Release automation is deliberately disarmed unless the repository variable
`SEMANTIC_RELEASE_ENABLED` is exactly `true`. Set it only after all activation
prerequisites have been reviewed:

- a pending or existing PyPI Trusted Publisher is configured for
`offendingcommit/hermes-plugin-kit`, workflow `release.yml`, and environment
`pypi`;
- the protected GitHub environment is named exactly `pypi`;
- GitHub immutable releases are enabled for the repository; the workflow
verifies the repository control with Administration-read permission after a
release intent is materialized but before source promotion, and verifies
`isImmutable` after publication. Non-releasing commits never enter a
protected environment;
- a protected environment named exactly `source-promotion` contains variable
`SOURCE_PROMOTION_APP_CLIENT_ID` and secret
`SOURCE_PROMOTION_APP_PRIVATE_KEY` for a dedicated GitHub App installed only
on this repository. The App has repository Administration read and Contents
write permissions; no PAT is used;
- the `main` ruleset requires the ordinary test workflow and names that
dedicated GitHub App as its sole source-promotion bypass actor. Generic
Actions credentials and the default `GITHUB_TOKEN` must not bypass it;
- the existing `0.7.0` source baseline has a reviewed immutable `v0.7.0` tag.

This change does not create or mutate any of those external controls. At review
time immutable releases were disabled, the required environments/ruleset/App
were not configured, and the PyPI project did not yet exist; a pending Trusted
Publisher supports that first OIDC publication. Keep
`SEMANTIC_RELEASE_ENABLED` unset until the full checklist is configured and a
generic-token rejection plus dedicated-App promotion have been exercised in an
isolated validation. A missing switch, control, baseline tag, test, build,
metadata check, source identity, or artifact hash stops before publication. The
final release receipt is the discoverable boundary for downstream
qualification; polling and recovery from a missed notification belong to that
downstream system.

## License

Expand Down
66 changes: 66 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
set shell := ["bash", "-cu"]

uv := env_var_or_default("UV", "uv")
hermes_agent_repo := env_var_or_default("HERMES_AGENT_REPO", "https://github.com/NousResearch/hermes-agent.git")
hermes_agent_dir := env_var_or_default("HERMES_AGENT_DIR", ".hermes-agent")

# Show available recipes.
default:
@just --list

# Create or sync the uv-managed environment.
install:
{{ uv }} sync

# Run the full unittest suite.
test:
{{ uv }} run python -m unittest discover -s tests

# Run one unittest by dotted test name.
test-one test_name:
{{ uv }} run python -m unittest {{ test_name }}

# Run deterministic release intent, identity, and workflow contracts.
test-release:
{{ uv }} run python -m unittest tests.test_release_contract -v

# Prepare a local Hermes checkout unless the caller supplied one.
[private]
prepare-hermes-agent:
#!/usr/bin/env bash
set -euo pipefail
if [[ -n "${HERMES_AGENT_PATH:-}" ]]; then
exit 0
elif [[ -d "{{ hermes_agent_dir }}/.git" ]]; then
echo "Updating {{ hermes_agent_dir }}"
git -C "{{ hermes_agent_dir }}" pull --ff-only -q || true
else
echo "Cloning hermes-agent into {{ hermes_agent_dir }}"
git clone --depth 1 "{{ hermes_agent_repo }}" "{{ hermes_agent_dir }}"
fi

# Run contract tests against an existing or locally managed Hermes checkout.
test-contract: prepare-hermes-agent
#!/usr/bin/env bash
set -euo pipefail
hermes_path="${HERMES_AGENT_PATH:-{{ hermes_agent_dir }}}"
HERMES_AGENT_PATH="$(cd "$hermes_path" && pwd)" \
{{ uv }} run python -m unittest tests.test_hermes_contract -v

# Run the context-engine contract against HERMES_AGENT_PATH.
test-context-engine-contract:
{{ uv }} run python -m unittest tests.test_context_engine_contract -v

# Build the wheel and source distribution.
build:
rm -rf dist
{{ uv }} build

# Validate wheel and source-distribution metadata.
check-dist:
{{ uv }} run twine check dist/*

# Remove Python caches and build artifacts.
clean:
find . -type d -name __pycache__ -prune -exec rm -rf {} +
rm -rf .pytest_cache .coverage htmlcov dist build *.egg-info
Loading