Skip to content
Open
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
13 changes: 10 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Tracker file — do not commit (changes every run)
# Compiled binary — do not commit
scripts/zelvinator/zelvinator

# Old tracker files (legacy, kept for migration reference)
.zelvinator-processed.txt
.zelvinator-ci-attempts.txt
*.lock

.zelvinator-*
scripts/zelvinator/zelvinator

# SQLite state database (lives outside the repo now, but just in case)
*.db
*.db-wal
*.db-shm
234 changes: 125 additions & 109 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,152 +1,168 @@
# Zelvinator Bot Scripts

Automation scripts for the [zelvinator](https://github.com/zelvinator) GitHub bot,
driven by [Hermes Agent](https://hermes-agent.nousresearch.com) cron jobs.
Automation scripts for the [zelvinator](https://github.com/zelvinator) GitHub bot, driven by Hermes Agent cron jobs.

## Overview

The bot reacts to `@zelvinator` mentions and issue assignments across ANY repo
the bot account has access to. No TARGET_ORGS config needed — just invite the
`zelvinator` GitHub user as a collaborator to your repo/org, and the bot
automatically picks up interactions on the next cron cycle.
The bot watches repositories across configured GitHub orgs for `@zelvinator` mentions and responds automatically using a **two-model architecture**:

**Reacts to:**
- `@zelvinator` in issue/PR body or title → implements feature / reviews PR
- `@zelvinator` in issue/PR comments or review comments → replies specifically
- Issue assigned to `zelvinator` user → implements the feature
- Bot's own PR with failing CI → diagnoses and fixes (max 3 attempts)
- **Qwen 3.6 35B** (worker) — discovery, triage, simple fixes, implementation, file-level review
- **GLM 5.2** (planner) — architectural planning, complex review, takeover fixes

## Model: People-reacting, not org-watching
### Architecture

The bot does NOT need to know which orgs or repos to watch. It searches ALL
repos its GitHub token has access to using:

| Detection step | Method |
|---|---|
| Issues/PRs mentioning @zelvinator | Search API (across all accessible repos) |
| Comments mentioning @zelvinator | Search API + comment verification (whitelisted users only) |
| PR review comments | Iterates open PRs, checks inline review comments |
| CI failures on bot's PRs | Issues API (`filter=created`) — bot's own PRs |
| Assigned issues | Issues API (`filter=assigned`) — issues assigned to bot |
```
┌─────────────────────────────────────────────────────────────┐
│ zelvinator-worker (Qwen 3.6, every 5 min) │
│ │
│ Phase 1: Discovery + Triage │
│ ├── Run Go binary → discover items → SQLite: discovered │
│ ├── Post 🐢 acknowledgment comment │
│ ├── Simple items (comments, ≤2 file fixes) → handle direct │
│ └── Complex items → state: needs_planning │
│ │
│ Phase 2: Implementation (pick up GLM's plans) │
│ ├── Queue items in "planned" or "fix_needed" state │
│ ├── Read plan, implement file-by-file, push, open PR │
│ └── state: review_pending │
│ │
│ Phase 3: Review Triage │
│ ├── Review diffs at file level │
│ ├── Clean → done | Simple fix → fix_needed │
│ └── Complex → needs_review (escalate to GLM) │
│ │
│ Phase 4: Stale reset (items stuck >20 min) │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ zelvinator-planner (GLM 5.2, every 15 min) │
│ │
│ Phase 1: Planning (items Qwen escalated) │
│ ├── Analyze codebase, write structured plan JSON │
│ └── state: planned │
│ │
│ Phase 2: Complex Review (items Qwen escalated) │
│ ├── Architectural review of diffs │
│ └── done | fix_needed | needs_planning (re-plan) │
│ │
│ Phase 3: Takeover (Qwen failed twice) │
│ └── GLM implements the fix directly → done │
└─────────────────────────────────────────────────────────────┘
```

## Prompt Injection Defense
### State Machine

User-supplied content (issue bodies, comments, titles) could contain prompt
injection attacks. Two-tier defense:
Items flow through a SQLite-backed state machine at `~/.hermes/zelvinator-bot/state.db`:

**Tier 1 — Content boundary markers (Go binary):** All user-controlled fields
are wrapped in `╔═══ USER-SUPPLIED CONTENT ═══╗` markers before reaching the
LLM, making the data/instruction boundary visually unambiguous. Structural
anomalies (zero-width Unicode chars, encoded payloads) trigger a
`content_warning` flag.
```
discovered → needs_planning → planned → implementing → review_pending → done
↑ ↓ ↑
│ fix_needed ──────────┘
│ ↓
│ needs_review → done
│ ↓
└──────── fix_needed (Qwen retry)
GLM takeover → done
```

**Tier 2 — Subagent judge (cron prompt):** Flagged items are NOT processed
directly. Instead, a zero-tools subagent (no MCP, no terminal, no filesystem)
is spawned solely to classify the content as SAFE or INJECTION. Only SAFE
items proceed to processing.
Key improvement over the old flat-file tracker: **claim-on-complete, not claim-on-discover**. Items enter `discovered` state but only move to `done` after work finishes. If a cron session dies mid-implementation, the item stays in `implementing` and gets reset to `planned` for retry on the next cycle.

## Scripts

### `scripts/find-zelvinator-mentions.sh`

Thin wrapper that delegates to the Go binary. Falls back to the Bash
implementation if the binary is missing.
Discovers new @zelvinator mentions across all configured repos. Inserts items into the SQLite state database. Only newly discovered items are returned.

**Usage:**
```bash
# Find new mentions (outputs JSON array of unprocessed items)
./scripts/find-zelvinator-mentions.sh

# Reset processed-items tracker
./scripts/find-zelvinator-mentions.sh --reset
```

**Output:** JSON array with items containing:
- `type` — `"issue"` or `"pr"`
- `repo` — `"owner/name"`
- `number`, `title`, `url`
- `trigger_source` — `"body"`, `"comment"`, `"assignment"`, `"ci_failure"`, or `"review_comment"`
- `trigger_comment` — the comment text that triggered (when applicable)
- `content_warning` — set to `"structural_anomaly"` if suspicious patterns detected

### `scripts/find-zelvinator-mentions.sh.bash`
### Zelvinator CLI (`scripts/zelvinator/`)

Standalone Bash implementation of the detection logic. Used as fallback if the
Go binary is not compiled. Same behavior, all logic in pure Bash + `gh` CLI.
The Go binary provides all state management and GitHub actions:

### `scripts/zelvinator/` (Go binary source)
```bash
# Discovery
zelvinator find # Discover new items, insert into SQLite

# State queries
zelvinator queue --state=discovered # Items to triage
zelvinator queue --state=planned # Items ready for implementation
zelvinator queue --state=fix_needed # Items needing fixes
zelvinator queue --state=review_pending # Items awaiting review
zelvinator queue --state=needs_planning # Items Qwen escalated to GLM
zelvinator queue --state=needs_review # Reviews Qwen escalated to GLM

# State transitions
zelvinator state <id> <new_state> [--plan=<file>] [--feedback=<text>] [--pr-url=<url>] [--error=<text>]

# Plan management
zelvinator plan <id> # Get plan JSON for an item

# Maintenance
zelvinator stale --reset # Reset items stuck in "implementing" >20 min
zelvinator stats # Show item counts per state
zelvinator reset --confirm # Reset entire state database

# GitHub actions
zelvinator comment <repo> <number> <body>
zelvinator review <repo> <number> <body> [event]
zelvinator reply-review <repo> <number> <review_comment_id> <body>
```

The primary detection engine. Written in Go for performance and reliability.
### Rebuilding the Go binary

| File | Purpose |
|---|---|
| `main.go` | Entry point, command dispatch |
| `find.go` | Discovery of mentions, assignments, CI failures |
| `comment.go` | Post comments, reviews, reply to review comments |
| `cifix.go` | Diagnose and fix CI failures on bot PRs |
| `internal/config/config.go` | Config loader (WHITELIST_USERS only) |
| `internal/github/client.go` | GitHub API client (go-github wrapper) |
| `internal/tracker/tracker.go` | Atomic claim tracker (deduplication) |
```bash
cd scripts/zelvinator
go build -o zelvinator .
```

## Configuration

Edit `config.sh`:

```bash
# Users whose @zelvinator mentions trigger bot actions
WHITELIST_USERS=(Hnatekmar xbedna MichalPustka mroncka)

# Path to Hermes .env file
HERMES_ENV="${HERMES_HOME:-$HOME/.hermes}/.env"
```

**There is no TARGET_ORGS.** The bot searches all repos its token can see.
To add the bot to a new repo, invite the `zelvinator` GitHub user as a
collaborator (no code changes needed).
| Variable | Purpose |
|----------|---------|
| `WHITELIST_USERS` | Users whose @zelvinator mentions trigger bot actions |
| `TARGET_ORGS` | GitHub orgs/accounts to search |
| `HERMES_ENV` | Path to Hermes .env file (normally `~/.hermes/.env`) |

## Credentials

The bot needs a single `GITHUB_TOKEN` in `~/.hermes/.env`:
```
GITHUB_TOKEN=ghp_...
```

The token determines which repos the bot can see. Must have `repo` scope
(full control of private repos).
The bot reads `GITHUB_TOKEN` from `~/.hermes/.env`. No secrets stored in this repo.

## Cron Setup
## Cron Integration

The bot runs as a Hermes cron job (`zelvinator-mentions`) every 10 minutes.
Set it up with:
Two Hermes cron jobs:

```bash
cronjob action=create \
name=zelvinator-mentions \
schedule='*/10 * * * *' \
deliver=local \
enabled_toolsets='["terminal","file","web","search"]' \
workdir=/root/workspace/zelvinator \
prompt="..."
```
| Job | Model | Schedule | Role |
|-----|-------|----------|------|
| `zelvinator-worker` | Qwen 3.6 35B | every 5 min | Discovery, triage, implementation, file-level review |
| `zelvinator-planner` | GLM 5.2 | every 15 min | Planning, complex review, takeover fixes |

See [references/cron-prompt.md](references/cron-prompt.md) for
the full cron prompt text (including injection defense guardrails).
Cron prompts are at:
- `references/cron-prompt-worker.md` — Qwen worker prompt
- `references/cron-prompt-planner.md` — GLM planner prompt

The cron job must run as the user who has:
- Access to `~/.hermes/.env` (contains GITHUB_TOKEN)
- The Go binary compiled at `~/.hermes/zelvinator-bot/scripts/zelvinator/zelvinator`
- `gh` CLI authenticated (optional, for fallback script)
## State Database

## Rebuilding the Go binary
SQLite database at `~/.hermes/zelvinator-bot/state.db` — **outside the git working directory**, immune to `git stash`/`checkout`/`reset`.

After pulling new source:
```bash
cd ~/.hermes/zelvinator-bot/scripts/zelvinator
go build -o zelvinator .
Schema:
```sql
CREATE TABLE items (
id TEXT PRIMARY KEY, -- "issue:owner/repo#123"
repo TEXT NOT NULL,
number INTEGER NOT NULL,
type TEXT NOT NULL, -- "issue" | "pr"
trigger_source TEXT NOT NULL,
state TEXT NOT NULL, -- discovered → ... → done
plan TEXT, -- JSON plan from GLM
review_feedback TEXT, -- GLM's review notes
pr_url TEXT,
attempts INTEGER DEFAULT 0,

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zelvinator Could there be guard against it going to negative?

...
);
```

## Adding the bot to a new repo

1. Invite the `zelvinator` GitHub user as a collaborator to the repo
2. Ensure the bot's `GITHUB_TOKEN` has access (repo scope)
3. That's it — the bot picks it up on the next 10-minute cron cycle
16 changes: 9 additions & 7 deletions config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,7 @@
# config.sh — Config for zelvinator bot scripts
# Source this from scripts that need these values.
#
# The bot no longer watches specific orgs. Instead, it searches ALL repos
# the bot token has access to. Invite the zelvinator GitHub user as a
# collaborator to any repo/org, then interact by:
# - Mentioning @zelvinator in an issue, PR, or comment
# - Assigning an issue to the zelvinator user
#
# Only whitelisted users' @zelvinator mentions trigger bot actions.
# Edit these lists to control which users and orgs the bot watches.

# Users whose @zelvinator mentions will trigger bot actions
WHITELIST_USERS=(
Expand All @@ -18,5 +12,13 @@ WHITELIST_USERS=(
mroncka
)

# GitHub orgs/accounts to search for @zelvinator mentions
TARGET_ORGS=(
zelvinator
Hnatekmar
hnatekmarorg
Algovectra
)

# Path to Hermes .env file (for GITHUB_TOKEN)
HERMES_ENV="${HERMES_HOME:-$HOME/.hermes}/.env"
Loading