Add test to confirm we can downgrade to older harperdb #10
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Claude Mention Handler | |
| # Responds to `@claude …` in PR comments and PR review (inline) comments. | |
| # Claude enters the action's "agent mode": reads the commenter's request, | |
| # uses the PR/issue as context, and can edit + commit + push. Gated to | |
| # HarperFast org members/collaborators so external contributors can't | |
| # trigger work against the repo. | |
| on: | |
| issue_comment: | |
| types: [created] | |
| pull_request_review_comment: | |
| types: [created] | |
| concurrency: | |
| group: claude-mention-${{ github.event.issue.number || github.event.pull_request.number }} | |
| cancel-in-progress: false | |
| jobs: | |
| work: | |
| # Belt-and-suspenders gate: | |
| # 1. Comment must contain the trigger phrase. | |
| # 2. Commenter must be HarperFast org OWNER / MEMBER or a repo | |
| # COLLABORATOR (the action also performs its own write-access | |
| # check on the actor as a fallback). | |
| if: >- | |
| contains(github.event.comment.body, '@claude') && | |
| contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), | |
| github.event.comment.author_association) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| permissions: | |
| # Write access is intentional — mention mode is "do work", which | |
| # means editing files, committing, and pushing (either to the PR's | |
| # branch or a new claude/… branch for issue-originated asks). | |
| contents: write | |
| pull-requests: write | |
| issues: write | |
| id-token: write | |
| steps: | |
| - name: Checkout | |
| # Default shallow fetch (depth 1). The agent can commit and push on a | |
| # shallow clone; `git log` / `git blame` aren't reached for by the | |
| # current prompt. Bump to a deeper fetch only if we see the agent | |
| # blocked on history lookups. | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - name: Parse mention | |
| # Real precision gate (the job-level `if:` is a cheap pre-filter). | |
| # Enforces: | |
| # 1. `@claude` must be the FIRST non-whitespace token (word- | |
| # boundary after) — rules out `@claudette`, inline prose | |
| # mentions ("saw @claude's fix"), and quoted replies | |
| # (`> @claude ...`) where the reply is addressing a human. | |
| # 2. Case-insensitive word-boundary `deep` anywhere in the body | |
| # → escalate to Opus. Sonnet is the default. | |
| id: mention | |
| env: | |
| BODY: ${{ github.event.comment.body }} | |
| run: | | |
| set -uo pipefail | |
| if ! printf '%s' "$BODY" | grep -Pqz '\A\s*@claude\b'; then | |
| echo "Comment does not start with @claude; skipping." | |
| echo "proceed=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if printf '%s' "$BODY" | grep -Piq '\bdeep\b'; then | |
| echo "model=claude-opus-4-7" >> "$GITHUB_OUTPUT" | |
| echo "Selected claude-opus-4-7 (deep requested)" | |
| else | |
| echo "model=claude-sonnet-4-6" >> "$GITHUB_OUTPUT" | |
| echo "Selected claude-sonnet-4-6 (default)" | |
| fi | |
| echo "proceed=true" >> "$GITHUB_OUTPUT" | |
| - name: Clone shared Harper skills | |
| # Pinned to a SHA so agent behavior is reproducible across runs — | |
| # updates require an explicit pin bump here. | |
| if: steps.mention.outputs.proceed == 'true' | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| with: | |
| repository: HarperFast/skills | |
| ref: d2db99bb37a6dde868cbc5ac81ca4146be8956fb # 1.3.0 (2026-04-16) | |
| path: .harper-skills | |
| - name: Setup Node.js | |
| # Needed so the agent can run `npm ci` / `npm run <script>` when a | |
| # specific mention actually requires it. We DON'T eagerly `npm ci` | |
| # here — most mentions (explain, review, tiny edits) don't need | |
| # deps, and ~35-60s install cost × every mention adds up. The | |
| # prompt tells the agent to run `npm ci` itself before any script | |
| # that needs dependencies. | |
| if: steps.mention.outputs.proceed == 'true' | |
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 | |
| with: | |
| node-version: '22' | |
| cache: 'npm' | |
| - name: Claude (agent mode) | |
| if: steps.mention.outputs.proceed == 'true' | |
| id: claude-agent | |
| uses: anthropics/claude-code-action@c3d45e8e941e1b2ad7b278c57482d9c5bf1f35b3 # v1.0.99 | |
| with: | |
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| show_full_output: true | |
| claude_args: | | |
| --model ${{ steps.mention.outputs.model }} | |
| --max-turns 48 | |
| # Tool allowlist is a security boundary — every entry is a | |
| # potential RCE primitive if a prompt injection succeeds. | |
| # Deliberately ABSENT: | |
| # * `Bash(npx:*)` — would let an injected instruction run | |
| # arbitrary published packages. | |
| # Deliberately TIGHTENED: | |
| # * `Bash(npm install)` (no-arg, not `Bash(npm install:*)`) — | |
| # blocks `npm install @attacker/<name>`. BUT: the agent | |
| # also has `Write`/`Edit` on package.json. A successful | |
| # injection could add a malicious `postinstall` script | |
| # and then invoke bare `npm install` to execute it, with | |
| # GITHUB_TOKEN + the claude[bot] installation token | |
| # reachable from the subprocess. This allowlist ALONE | |
| # does not close that path — branch protection + the | |
| # author_association gate are what actually bound blast | |
| # radius. A future PR may add `ignore-scripts=true` via | |
| # .npmrc and/or drop `Bash(npm install)` entirely, | |
| # deferring installs to a separate CI job. | |
| --allowedTools "Read,Write,Edit,Grep,Glob,mcp__github_inline_comment__create_inline_comment,Bash(gh pr view:*),Bash(gh pr diff:*),Bash(gh pr comment:*),Bash(gh pr checkout:*),Bash(gh pr create:*),Bash(gh issue view:*),Bash(gh issue comment:*),Bash(git:*),Bash(npm install),Bash(npm ci:*),Bash(npm run:*),Bash(npm test:*)" | |
| # In agent mode the action can use the triggering comment as the | |
| # prompt, but we inline it explicitly below to guarantee the agent | |
| # always has PR/issue number, URL, and the commenter's exact | |
| # request. | |
| # | |
| # TODO: revisit if a future claude-code-action release reliably | |
| # forwards the triggering comment as the prompt. | |
| prompt: | | |
| You were invoked via an `@claude` mention on ${{ github.repository }}. | |
| ## Mention context | |
| - Repo: ${{ github.repository }} | |
| - Target number: #${{ github.event.issue.number || github.event.pull_request.number }} | |
| - Target URL: ${{ github.event.issue.html_url || github.event.pull_request.html_url }} | |
| - Target kind: ${{ github.event.issue.pull_request && 'pull request' || (github.event.pull_request && 'pull request' || 'issue') }} | |
| - Commenter: @${{ github.event.comment.user.login }} | |
| The commenter wrote (verbatim, including any multi-line content): | |
| ``` | |
| ${{ github.event.comment.body }} | |
| ``` | |
| Start by reading the target so you have real context: | |
| - For a PR: `gh pr view <N>` then `gh pr diff <N>` if you need | |
| the changes. | |
| - For an issue: `gh issue view <N>`. | |
| Then act on the request. If the request is "review this PR", | |
| follow the review discipline from HarperFast/ai-review-prompts | |
| (see .github/workflows/claude-review.yml for the layered | |
| scope this repo uses) — do NOT treat review as a code-edit task. | |
| ## Conventions | |
| Read the repo's agent context files first (commonly | |
| `CLAUDE.md`, `AGENTS.md`, or similar at the repo root). Their | |
| conventions and gotchas apply to any code you write. Match | |
| the repo's existing style rather than introducing a new one. | |
| Harper-specific notes for code work: | |
| - Linter is oxlint, not eslint. `npm run lint` runs oxlint. | |
| - Primary storage is RocksDB (LMDB is the alternate via | |
| `HARPER_STORAGE_ENGINE=lmdb`). | |
| - TypeStrip-compatible (`erasableSyntaxOnly`). Don't use | |
| TypeScript features that break typestrip. | |
| - `dependencies.md` documents all npm packages; if you add | |
| a runtime dep, add an entry there. | |
| ## Before committing | |
| Scale validation to the kind of change you made: | |
| - Doc-only change (only `*.md` / `README.md` / `CLAUDE.md` / | |
| `AGENTS.md` / `dependencies.md`, or `package.json` | |
| keyword/description edits): run `npm run format:check` and | |
| `npm run lint`. Do NOT run `npm run build` / | |
| `npm run test:unit` — they are not affected and waste turns. | |
| - Code change that affects behavior: run `npm ci` first | |
| (deps aren't pre-installed in this workflow), then | |
| `npm run build && npm run lint && npm run format:check && npm run test:unit`. | |
| Fix anything that fails. Integration tests | |
| (`npm run test:integration`) are slow; run them only if | |
| the change plausibly affects integration behavior. | |
| When in doubt, err toward the fuller validation. | |
| ## Output | |
| - Scope your changes to exactly what the mention asked for. | |
| Don't refactor unrelated code. | |
| - Commit with a descriptive message referencing the | |
| issue/PR. | |
| - If the request is ambiguous or you have to make a | |
| judgment call that changes public API or semantics, post | |
| a comment on the PR/issue explaining your interpretation | |
| and stop — do NOT push speculative changes. | |
| - Do NOT use REQUEST_CHANGES or APPROVE on PRs. Post | |
| comments or push commits only. |