expr: implement hashFiles()Verified

hashFiles(pattern...) now globs the patterns against the workspace root and
returns a SHA-256 over the matched files (per-file sha256 combined, sorted) —
deterministic and content-sensitive (not byte-identical to GitHub's value;
cache keys are cave-local). "" when nothing matches.

- Hand-rolled glob matcher (** = any depth, * / ? within a segment; leading ./
  ignored) since cl-ppcre isn't a dep.
- The host-side workspace root is threaded into the ${{ }} context as the
  keyword key :workspace-root (never collides with a GHA context) via
  %gha-context; the runner passes workdir at both eval sites.

Verified: **/glob matches at any depth, distinct hashes per pattern set,
empty string on no match, determinism, and hash changes when a file changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Anthony Green2026-06-29 11:45:03 -0400 87637e4c9d5660fa743d5acb4e678d72d169e168
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>