diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8118bf1..7b9ebc0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,31 +63,6 @@ jobs: - run: python -m ruff format --check . - run: python -m pytest -q - # Proves the README's own quick start still works: pytest (see tests/test_quickstart_block.py) - # already proves the extractor fails a broken block, so this job just has to run the real one. - quickstart: - name: Quick start verified - runs-on: ubuntu-latest - # Reads the checkout; writes nothing. - permissions: - contents: read - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - with: - persist-credentials: false - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 - with: - python-version: "3.12" - # Installed from this checkout, not PyPI, so the README's own `pip install context-report` - # line finds itself already satisfied and does not fetch a different released version. - - run: pip install . - - name: Extract and run the README's quickstart block - run: | - workdir=$(mktemp -d) - python3 tools/quickstart_block.py README.md > "$workdir/quickstart.sh" - cd "$workdir" - bash -euo pipefail quickstart.sh - # This repo is itself a chock adopter (see AGENTS.md, .agents/policies/): it governs # its own contributions the same way chock and chock-catalog do. Checking it here # proves the adoption on this repo rather than only in chock's own tests, and closes diff --git a/.github/workflows/demo-gif.yml b/.github/workflows/demo-gif.yml new file mode 100644 index 0000000..69d27c4 --- /dev/null +++ b/.github/workflows/demo-gif.yml @@ -0,0 +1,92 @@ +name: Render demo GIF +# Two ways in: workflow_dispatch proves docs/assets/demo.gif is reproducible from +# docs/assets/demo.tape without touching any branch (download the demo-gif artifact and +# commit it by hand). A push touching the tape on a non-default branch renders and commits +# the regenerated GIF back to that branch, so a tape change carries its GIF into its own +# pull request -- main is protected with no bypass, so this workflow can never push there. +on: + workflow_dispatch: + push: + paths: ["docs/assets/demo.tape"] + branches-ignore: [main] +permissions: {} +jobs: + render: + name: Render docs/assets/demo.tape + if: github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + with: + python-version: "3.12" + - run: pip install . + # No verified pin for charmbracelet/vhs-action was available to this change (see the PR + # description's Deviations); this reproduces the same tape with the plain VHS binary + # instead, using the release URL already pinned in docs/README-refresh's render recipe. + - name: Install ttyd, ffmpeg and vhs + # Checksum from charmbracelet/vhs's own v0.10.0 release checksums.txt -- verified before + # this pin was added; a corrupted or substituted download fails the sha256sum check below. + run: | + sudo apt-get update -qq + sudo apt-get install -y -qq ttyd ffmpeg + curl -sSL -o /tmp/vhs.tgz https://github.com/charmbracelet/vhs/releases/download/v0.10.0/vhs_0.10.0_Linux_x86_64.tar.gz + echo "b552c3870aca101dcafe533cfef32dceb7b783400ad32642e728775c9f125407 /tmp/vhs.tgz" | sha256sum -c - + tar xzf /tmp/vhs.tgz -C /tmp + sudo install -m 0755 /tmp/vhs_0.10.0_Linux_x86_64/vhs /usr/local/bin/vhs + - name: Render as a non-root user (Chromium refuses to run as root) + run: | + sudo useradd -m vhsuser + sudo chmod -R a+rX "$PWD" + sudo chmod 777 docs/assets + sudo su vhsuser -c "cd $PWD && vhs docs/assets/demo.tape" + sudo chown "$(id -u)":"$(id -g)" docs/assets/demo.gif + sudo chmod 755 docs/assets + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: demo-gif + path: docs/assets/demo.gif + + render-and-commit: + name: Render docs/assets/demo.tape and commit the GIF + if: github.event_name == 'push' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: true + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + with: + python-version: "3.12" + - run: pip install . + # No verified pin for charmbracelet/vhs-action was available to this change (see the PR + # description's Deviations); this reproduces the same tape with the plain VHS binary + # instead, using the release URL already pinned in docs/README-refresh's render recipe. + - name: Install ttyd, ffmpeg and vhs + # Checksum from charmbracelet/vhs's own v0.10.0 release checksums.txt -- verified before + # this pin was added; a corrupted or substituted download fails the sha256sum check below. + run: | + sudo apt-get update -qq + sudo apt-get install -y -qq ttyd ffmpeg + curl -sSL -o /tmp/vhs.tgz https://github.com/charmbracelet/vhs/releases/download/v0.10.0/vhs_0.10.0_Linux_x86_64.tar.gz + echo "b552c3870aca101dcafe533cfef32dceb7b783400ad32642e728775c9f125407 /tmp/vhs.tgz" | sha256sum -c - + tar xzf /tmp/vhs.tgz -C /tmp + sudo install -m 0755 /tmp/vhs_0.10.0_Linux_x86_64/vhs /usr/local/bin/vhs + - name: Render as a non-root user (Chromium refuses to run as root) + run: | + sudo useradd -m vhsuser + sudo chmod -R a+rX "$PWD" + sudo chmod 777 docs/assets + sudo su vhsuser -c "cd $PWD && vhs docs/assets/demo.tape" + sudo chown "$(id -u)":"$(id -g)" docs/assets/demo.gif + sudo chmod 755 docs/assets + - uses: stefanzweifel/git-auto-commit-action@4a55954c782fc1ea30b9056cd3e7a2b40ca8887d # v7.2.0 + with: + commit_message: "docs: regenerate demo.gif" + file_pattern: docs/assets/demo.gif diff --git a/.github/workflows/quickstart.yml b/.github/workflows/quickstart.yml new file mode 100644 index 0000000..c36118d --- /dev/null +++ b/.github/workflows/quickstart.yml @@ -0,0 +1,48 @@ +name: Quick start +on: + push: + branches: [main] + pull_request: +permissions: {} +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + # Proves the README's own quick start still works, and that docs/quickstart.sh (the same + # commands, committed for anyone who wants a runnable script) has not drifted from it. + # tools/quickstart_block.py stays the single source: this job regenerates + # docs/quickstart.sh from the README and diffs it before running it. + quickstart: + name: Quick start verified + runs-on: ubuntu-latest + # Reads the checkout; writes nothing (docs/quickstart.sh is checked for drift, not committed). + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + with: + python-version: "3.12" + # Installed from this checkout, not PyPI, so the README's own `pip install context-report` + # line finds itself already satisfied and does not fetch a different released version. + - run: pip install . + - name: Regenerate docs/quickstart.sh and check it matches the committed copy + run: | + { + printf '#!/usr/bin/env bash\n' + printf 'set -euo pipefail\n' + printf '# Generated from the README.md quickstart block by tools/quickstart_block.py. Do not edit by hand.\n' + python3 tools/quickstart_block.py README.md + } > /tmp/quickstart.sh.generated + if ! diff -u docs/quickstart.sh /tmp/quickstart.sh.generated; then + echo "::error::docs/quickstart.sh is stale -- regenerate it from README.md's quickstart block (tools/quickstart_block.py) and commit it." + exit 1 + fi + - name: Run docs/quickstart.sh in a fresh directory + run: | + workdir=$(mktemp -d) + cp docs/quickstart.sh "$workdir/quickstart.sh" + cd "$workdir" + bash -euo pipefail quickstart.sh diff --git a/.github/workflows/render-demo.yml b/.github/workflows/render-demo.yml deleted file mode 100644 index 3da7144..0000000 --- a/.github/workflows/render-demo.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Render demo GIF -# Dispatch-only: this never commits to main (protected), it just proves docs/assets/demo.gif is -# reproducible from docs/assets/demo.tape. Download the demo-gif artifact and commit it by hand. -on: - workflow_dispatch: -permissions: {} -jobs: - render: - name: Render docs/assets/demo.tape - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - with: - persist-credentials: false - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 - with: - python-version: "3.12" - - run: pip install . - # No verified pin for charmbracelet/vhs-action was available to this change (see the PR - # description's Deviations); this reproduces the same tape with the plain VHS binary - # instead, using the release URL already pinned in docs/README-refresh's render recipe. - - name: Install ttyd, ffmpeg and vhs - # Checksum from charmbracelet/vhs's own v0.10.0 release checksums.txt -- verified before - # this pin was added; a corrupted or substituted download fails the sha256sum check below. - run: | - sudo apt-get update -qq - sudo apt-get install -y -qq ttyd ffmpeg - curl -sSL -o /tmp/vhs.tgz https://github.com/charmbracelet/vhs/releases/download/v0.10.0/vhs_0.10.0_Linux_x86_64.tar.gz - echo "b552c3870aca101dcafe533cfef32dceb7b783400ad32642e728775c9f125407 /tmp/vhs.tgz" | sha256sum -c - - tar xzf /tmp/vhs.tgz -C /tmp - sudo install -m 0755 /tmp/vhs_0.10.0_Linux_x86_64/vhs /usr/local/bin/vhs - - name: Render as a non-root user (Chromium refuses to run as root) - run: | - sudo useradd -m vhsuser - sudo chmod -R a+rX "$PWD" - sudo chmod 777 docs/assets - sudo su vhsuser -c "cd $PWD && vhs docs/assets/demo.tape" - sudo chown "$(id -u)":"$(id -g)" docs/assets/demo.gif - sudo chmod 755 docs/assets - - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: demo-gif - path: docs/assets/demo.gif diff --git a/CHANGELOG.md b/CHANGELOG.md index 95af0a6..46105af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ Per in-toto convention, `0.X` versions are major: fields may change until 1.0. ## Unreleased +### CI + +- `.github/workflows/render-demo.yml` renamed to `demo-gif.yml`: the `workflow_dispatch` + trigger still renders and uploads the `demo-gif` artifact, and a new `push` trigger (on + `docs/assets/demo.tape`, non-default branches only) renders and commits the regenerated GIF + back to the branch, since `main` is protected with no bypass and cannot receive a workflow + push directly. +- The `quickstart` job moved out of `ci.yml` into its own `.github/workflows/quickstart.yml` + (`push`, `pull_request`). It still extracts and runs the README's fenced quickstart block via + `tools/quickstart_block.py`, and now also regenerates `docs/quickstart.sh` from that block and + fails if the committed copy has drifted, so the README stays the single source. + ### Docs - README: a rendered `produce`/`verify` demo GIF (`docs/assets/demo.tape`, reproducible via diff --git a/docs/quickstart.sh b/docs/quickstart.sh new file mode 100755 index 0000000..f895ad7 --- /dev/null +++ b/docs/quickstart.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail +# Generated from the README.md quickstart block by tools/quickstart_block.py. Do not edit by hand. +pip install context-report +mkdir -p my-plugin/hooks +cat > my-plugin/hooks/guard.py <<'PY' +#!/usr/bin/env python3 +import json, sys +event = json.load(sys.stdin) +command = event.get("tool_input", {}).get("command", "") +if "destructive-pattern" in command: + print(json.dumps({"decision": "deny", "reason": "blocked destructive command"})) +sys.exit(0) +PY +cat > my-plugin/hooks/hooks.json <<'JSON' +{ + "hooks": { + "PreToolUse": [ + {"hooks": [{"command": "python3", "args": ["${CLAUDE_PLUGIN_ROOT}/hooks/guard.py"]}]} + ] + } +} +JSON +context-report produce --subject ./my-plugin --kind plugin --target claude_code --n 20 --out report.json +context-report verify report.json --subject ./my-plugin