Skip to content

Commit 3ca0722

Browse files
authored
Merge pull request #3 from RedHatInsights/day-one-run
fix: polish instructions set after first day run.
2 parents f3c4209 + 67b6238 commit 3ca0722

25 files changed

Lines changed: 516 additions & 102 deletions

.env.example

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,22 @@ CLAUDE_CODE_USE_VERTEX=1
1111
ANTHROPIC_VERTEX_PROJECT_ID=your-gcp-project
1212
VERTEX_LOCATION=global
1313
ANTHROPIC_SMALL_FAST_MODEL=claude-sonnet-4-6
14+
15+
# Git identity — used for commits (optional, uses your local git config if unset)
16+
# GIT_AUTHOR_NAME=platex-rehor-bot
17+
# GIT_AUTHOR_EMAIL=platform-experience-services@redhat.com
18+
# GIT_COMMITTER_NAME=platex-rehor-bot
19+
# GIT_COMMITTER_EMAIL=platform-experience-services@redhat.com
20+
21+
# GPG signing key ID (optional, disables commit signing if unset)
22+
# GPG_SIGNING_KEY=0A22E27F31412FEE
23+
24+
# SSH keys — route different hosts through different keys (optional)
25+
# BOT_SSH_KEY=.ssh/id_ed25519
26+
# GITLAB_SSH_KEY=~/.ssh/id_ed25519
27+
28+
# GitHub — bot PAT for gh CLI (optional if gh auth login was used)
29+
# GH_TOKEN=ghp_...
30+
31+
# GitLab — personal PAT for glab CLI (optional if glab auth login was used)
32+
# GITLAB_TOKEN=glpat-...

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ memory-server/src/static/assets/
2121
memory-server/src/static/index.html
2222
sa-key.json
2323
.ssh/
24+
.gitconfig

ARCHITECTURE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ The agent communicates with external systems through [Model Context Protocol](ht
100100
| Server | Transport | Purpose |
101101
|--------|-----------|---------|
102102
| **mcp-atlassian** | stdio | Jira CRUD: search tickets, read/update issues, transitions, comments, sprints |
103-
| **bot-memory** | SSE (HTTP) | Task tracking (5 concurrent max) + RAG memory (vector search over past learnings) |
103+
| **bot-memory** | SSE (HTTP) | Task tracking (10 concurrent max) + RAG memory (vector search over past learnings) |
104104
| **chrome-devtools** | stdio | Browser automation for visual verification — navigate pages, take screenshots |
105105
| **hcc-patternfly-data-view** | stdio | PatternFly component docs (only loaded for frontend persona repos) |
106106

@@ -386,6 +386,6 @@ Both images use Red Hat UBI9 base images:
386386

387387
- Each bot instance handles one label (team). Multiple instances can run in parallel.
388388
- All instances share the memory server (cross-team learnings are possible).
389-
- Hard cap of 5 concurrent tasks per bot instance (enforced by memory server).
389+
- Hard cap of 10 concurrent tasks per bot instance (enforced by memory server).
390390
- Cycles are sequential within a bot — no concurrency within a single instance.
391391
- Idle interval (1 hour) keeps costs low when there's no work.

CLAUDE.md

Lines changed: 54 additions & 23 deletions
Large diffs are not rendered by default.

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: install run init dashboard costs costs-today costs-week seed-costs stop logs help memory-server memory-server-stop
1+
.PHONY: install run init dashboard costs costs-today costs-week seed-costs stop logs help memory-server memory-server-stop memory-dump memory-import
22

33
LABEL ?= hcc-ai-framework
44

OPERATIONS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ Check:
308308
1. Are there tickets with the right primary label? Use the Jira filter above
309309
2. Are the tickets unassigned?
310310
3. Do they have a `repo:` label matching `project-repos.json`?
311-
4. Is the bot at the 5-task capacity limit? Check the dashboard
311+
4. Is the bot at the 10-task capacity limit? Check the dashboard
312312

313313
### MCP server fails to connect
314314

README.md

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ JIRA_API_TOKEN=your-jira-api-token
3333
# Claude — GCP Vertex AI (service account)
3434
# Follow the RH internal guide to set up Vertex AI access
3535
# and generate a service account key file (sa-key.json).
36+
37+
# GitHub — bot PAT for gh CLI
38+
GH_TOKEN=ghp_...
39+
40+
# GitLab — personal PAT for glab CLI (api + write_repository scopes)
41+
GITLAB_TOKEN=glpat-...
3642
```
3743

3844
## Quick Start
@@ -135,29 +141,23 @@ The bot is a good developer but has zero tribal knowledge. Don't assume it knows
135141

136142
## Adding a new repo
137143

138-
1. Add to `project-repos.json`:
144+
All repos use forks by default. The bot pushes to the fork and opens PRs/MRs targeting the upstream repo.
145+
146+
1. Fork the repo under the bot's account (e.g. `platex-rehor-bot`):
147+
```bash
148+
gh repo fork RedHatInsights/my-repo --clone=false
149+
```
150+
2. Add to `project-repos.json`:
139151
```json
140152
"my-repo": {
141-
"url": "git@github.com:RedHatInsights/my-repo.git"
153+
"url": "git@github.com:platex-rehor-bot/my-repo.git",
154+
"upstream": "git@github.com:RedHatInsights/my-repo.git"
142155
}
143156
```
144-
2. Add a `repo:my-repo` label to the Jira ticket
145-
146-
The bot clones repos automatically when it picks up a ticket.
147-
148-
### Fork repos
149-
150-
If the bot doesn't have push access to the upstream repo, use a fork:
151-
152-
```json
153-
"app-interface": {
154-
"url": "git@gitlab.cee.redhat.com:youruser/app-interface.git",
155-
"upstream": "git@gitlab.cee.redhat.com:service/app-interface.git",
156-
"host": "gitlab"
157-
}
158-
```
157+
For GitLab repos, add `"host": "gitlab"`.
158+
3. Add a `repo:my-repo` label to the Jira ticket.
159159

160-
The bot clones from the fork, syncs from upstream, pushes branches to the fork, and opens MRs targeting the upstream repo.
160+
The bot clones repos automatically when it picks up a ticket. It fetches from `upstream`, creates branches based on the latest upstream code, pushes to `origin` (the fork), and opens PRs/MRs targeting the upstream repo.
161161

162162
### Persona selection
163163

@@ -167,7 +167,25 @@ Personas are NOT hardcoded to repos. The bot dynamically selects the best-fit pe
167167

168168
### Option A: Bot on host, memory server in Docker (recommended)
169169

170-
The recommended setup for development. The bot runs directly on your machine using your local Claude Code credentials, while the memory server runs in Docker:
170+
The recommended setup for development. The bot runs directly on your machine while the memory server runs in Docker.
171+
172+
#### 1. Configure `.env`
173+
174+
Copy `.env.example` to `.env` and fill in your credentials. All identity and auth settings are driven by `.env` — at startup, `run.py` reads these and auto-configures git and SSH.
175+
176+
**Git identity** — set `GIT_AUTHOR_NAME`, `GIT_AUTHOR_EMAIL`, `GIT_COMMITTER_NAME`, `GIT_COMMITTER_EMAIL` to commit as the bot account. If unset, your local git config is used.
177+
178+
**GPG signing** — import the bot's GPG key (`gpg --import <key-file>`), then set `GPG_SIGNING_KEY` to the key ID. If unset, commits are not signed.
179+
180+
**SSH keys** — set `BOT_SSH_KEY` and/or `GITLAB_SSH_KEY` to route git traffic through specific keys per host. Paths can be absolute or relative to the repo root. If unset, your default SSH agent is used.
181+
182+
**CLI auth** — set `GH_TOKEN` and/or `GITLAB_TOKEN`, or log in manually:
183+
```bash
184+
gh auth login # GitHub
185+
glab auth login --hostname gitlab.cee.redhat.com # GitLab
186+
```
187+
188+
#### 2. Start the services
171189

172190
```bash
173191
# Start memory server + postgres
@@ -177,8 +195,6 @@ make memory-server
177195
make run LABEL=hcc-ai-framework
178196
```
179197

180-
This is the easiest way to get started — no need for service account keys or bot-specific GitHub credentials. The bot uses your local Claude Code auth and your personal gh/glab CLI sessions.
181-
182198
### Option B: Full stack in Docker
183199

184200
For production-like deployments or CI — everything runs in containers with dedicated bot credentials:
@@ -258,7 +274,7 @@ Each repo has one or more personas that provide domain-specific guidelines. Pers
258274

259275
The bot has persistent memory via MCP:
260276

261-
- **Task tracking** — structured records of active work with status, PR links, and progress metadata. Hard cap of 5 concurrent active tasks. When interrupted mid-cycle, the bot saves progress (`last_step`, `next_step`, `files_changed`) so the next cycle resumes seamlessly.
277+
- **Task tracking** — structured records of active work with status, PR links, and progress metadata. Hard cap of 10 concurrent active tasks. When interrupted mid-cycle, the bot saves progress (`last_step`, `next_step`, `files_changed`) so the next cycle resumes seamlessly.
262278
- **RAG memory** — vector-searchable knowledge base of learnings from completed tickets, PR review feedback, and codebase patterns. The bot searches this before starting any new ticket, so it improves over time.
263279

264280
## Cost tracking

bot/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ def _resolve_env_vars(obj):
8484
"SSH_PRIVATE_KEY_B64",
8585
"GPG_PRIVATE_KEY_B64",
8686
"GOOGLE_SA_KEY_B64",
87+
"BOT_SSH_KEY",
88+
"GITLAB_SSH_KEY",
89+
"GPG_SIGNING_KEY",
8790
]
8891

8992

bot/run.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,91 @@
2121
DATA_DIR = SCRIPT_DIR / "data"
2222

2323

24+
def _resolve_path(p: str) -> str:
25+
"""Resolve a path that may be relative to SCRIPT_DIR, ~, or absolute."""
26+
path = Path(p).expanduser()
27+
if not path.is_absolute():
28+
path = SCRIPT_DIR / path
29+
return str(path.resolve())
30+
31+
32+
def setup_ssh(script_dir: Path) -> None:
33+
"""Generate SSH config from env vars and set GIT_SSH_COMMAND.
34+
35+
Reads BOT_SSH_KEY (for github.com) and GITLAB_SSH_KEY (for gitlab)
36+
from the environment. Skips if neither is set.
37+
"""
38+
bot_key = os.environ.get("BOT_SSH_KEY")
39+
gitlab_key = os.environ.get("GITLAB_SSH_KEY")
40+
41+
if not bot_key and not gitlab_key:
42+
return
43+
44+
ssh_dir = script_dir / ".ssh"
45+
ssh_dir.mkdir(exist_ok=True)
46+
config_path = ssh_dir / "config"
47+
48+
lines = ["# Auto-generated by bot/run.py — do not edit manually"]
49+
50+
if bot_key:
51+
lines += [
52+
"",
53+
"Host github.com",
54+
f" IdentityFile {_resolve_path(bot_key)}",
55+
" IdentitiesOnly yes",
56+
" StrictHostKeyChecking accept-new",
57+
]
58+
59+
if gitlab_key:
60+
lines += [
61+
"",
62+
"Host gitlab.cee.redhat.com",
63+
f" IdentityFile {_resolve_path(gitlab_key)}",
64+
" IdentitiesOnly yes",
65+
" StrictHostKeyChecking accept-new",
66+
]
67+
68+
config_path.write_text("\n".join(lines) + "\n")
69+
config_path.chmod(0o600)
70+
os.environ["GIT_SSH_COMMAND"] = f"ssh -F {config_path}"
71+
72+
73+
def setup_git(script_dir: Path) -> None:
74+
"""Generate a .gitconfig with identity and optional GPG signing.
75+
76+
Reads from env: GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, GPG_SIGNING_KEY.
77+
Sets GIT_CONFIG_GLOBAL so all repos use this config.
78+
"""
79+
name = os.environ.get("GIT_AUTHOR_NAME")
80+
email = os.environ.get("GIT_AUTHOR_EMAIL")
81+
82+
if not name and not email:
83+
return
84+
85+
config_path = script_dir / ".gitconfig"
86+
lines = [
87+
"# Auto-generated by bot/run.py — do not edit manually",
88+
"[user]",
89+
]
90+
if name:
91+
lines.append(f"\tname = {name}")
92+
if email:
93+
lines.append(f"\temail = {email}")
94+
95+
signing_key = os.environ.get("GPG_SIGNING_KEY")
96+
if signing_key:
97+
lines += [
98+
f"\tsigningkey = {signing_key}",
99+
"[commit]",
100+
"\tgpgsign = true",
101+
"[gpg]",
102+
"\tformat = openpgp",
103+
]
104+
105+
config_path.write_text("\n".join(lines) + "\n")
106+
os.environ["GIT_CONFIG_GLOBAL"] = str(config_path)
107+
108+
24109
def setup_logging() -> None:
25110
"""Configure logging to stdout and data/bot.log."""
26111
DATA_DIR.mkdir(exist_ok=True)
@@ -50,6 +135,10 @@ def main() -> None:
50135
if gac and not os.path.isabs(gac):
51136
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = str(SCRIPT_DIR / gac)
52137

138+
# Set up SSH and git identity from env vars (no-op if not configured)
139+
setup_ssh(SCRIPT_DIR)
140+
setup_git(SCRIPT_DIR)
141+
53142
parser = argparse.ArgumentParser(description="Dev bot agent loop")
54143
parser.add_argument(
55144
"--label",

dashboard/src/App.css

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,14 @@ header {
108108
background: var(--surface);
109109
margin-bottom: 12px;
110110
gap: 12px;
111-
flex-wrap: wrap;
111+
}
112+
.bot-banner.expanded {
113+
align-items: flex-start;
114+
}
115+
.bot-banner.expanded .banner-message {
116+
white-space: normal;
117+
overflow: visible;
118+
text-overflow: unset;
112119
}
113120

114121
.bot-banner.state-working {
@@ -245,6 +252,19 @@ header {
245252
.banner-updated {
246253
color: var(--text-dim);
247254
}
255+
.banner-toggle {
256+
background: none;
257+
border: none;
258+
color: var(--text-dim);
259+
cursor: pointer;
260+
font-size: 10px;
261+
padding: 2px 4px;
262+
line-height: 1;
263+
flex-shrink: 0;
264+
}
265+
.banner-toggle:hover {
266+
color: var(--text);
267+
}
248268

249269
/* Tab nav */
250270
.tab-nav {
@@ -350,6 +370,25 @@ select:focus,
350370
background: rgba(248, 81, 73, 0.1);
351371
}
352372

373+
.btn-unarchive {
374+
background: transparent;
375+
color: var(--accent);
376+
border: 1px solid var(--accent);
377+
padding: 6px 16px;
378+
border-radius: 6px;
379+
font-size: 13px;
380+
cursor: pointer;
381+
}
382+
.btn-unarchive:hover {
383+
background: rgba(88, 166, 255, 0.1);
384+
}
385+
386+
.detail-actions {
387+
display: flex;
388+
gap: 8px;
389+
margin-top: 16px;
390+
}
391+
353392
/* Card grid */
354393
.card-grid {
355394
display: flex;
@@ -388,6 +427,10 @@ select:focus,
388427
.task-card.status-paused {
389428
border-left-color: var(--text-dim);
390429
}
430+
.task-card.status-archived {
431+
border-left-color: var(--text-dim);
432+
opacity: 0.7;
433+
}
391434

392435
.task-card-header {
393436
display: flex;
@@ -427,6 +470,10 @@ select:focus,
427470
background: rgba(139, 148, 158, 0.2);
428471
color: var(--text-dim);
429472
}
473+
.status-badge.archived {
474+
background: rgba(139, 148, 158, 0.15);
475+
color: var(--text-dim);
476+
}
430477

431478
.task-card-title {
432479
font-size: 14px;

0 commit comments

Comments
 (0)