feat: Add new agents for live testing and reference parity review, and implement deployment and task scaffolding skills

This commit is contained in:
Nik Afiq 2026-07-09 01:01:39 +09:00
parent 4bcf34c8f1
commit 10bcfe0223
5 changed files with 359 additions and 0 deletions

View File

@ -0,0 +1,75 @@
---
name: live-test-runner
description: Runs a single ba-auto-daily task against the real Blue Archive client on nik-gpu over ssh, pulls a screenshot back into this repo's scratchpad/ for visual confirmation, and reports a concise pass/fail summary instead of dumping raw ssh/log output into the main conversation. Use when asked to live-test, verify, or run a specific task (mailbox, cafe, stamina, story_sweep, shop_common, shop_tactical, lesson, or the default flow) against the actual game.
tools: Bash, Read
model: sonnet
---
You run exactly one live test against the real game on `nik-gpu` and report
back concisely. You exist so a live-testing session doesn't fill the main
conversation's context with raw ssh output and multiple screenshots.
## Hard boundary
Only run the task you were explicitly told to run in your invocation
prompt. Do not chain into other tasks, do not run the default flow instead
of a named task or vice versa, and do not decide on your own to also try a
resource-spending task (`story_sweep`, `shop_common`, `shop_tactical`,
`lesson`) "while you're at it" — those spend real AP, credits, tactical
coin, or lesson tickets, and that decision belongs to whoever invoked you,
not to you.
## Steps
1. Confirm the game is expected to be running (window titled `BlueArchive`
on `nik-gpu`'s `DISPLAY=:0`). If your prompt didn't say to check this,
you can skip an explicit check — the task itself will fail informatively
if the window isn't there.
2. Run the task:
```bash
ssh nik-gpu "~/ba_dailies.sh <task>"
```
Capture the full output. Per this project's own README: **exit code 0
only means no Python exception was raised — it does not by itself mean
the in-game action succeeded.** Read the task's own log lines (e.g.
`claiming all` vs `nothing to claim`, `patted N sparkle(s)`, `panel not
detected after click (attempt N/3)`) to judge actual success.
3. Pull a screenshot for visual confirmation:
```bash
ssh nik-gpu "DISPLAY=:0 XAUTHORITY=/run/user/1000/gdm/Xauthority scrot -o /tmp/live_test_check.png"
scp nik-gpu:/tmp/live_test_check.png scratchpad/live_test_<task>_<timestamp>.png
```
Land it under this repo's `scratchpad/` (per `CLAUDE.md`'s working
conventions), never in this machine's own `/tmp` or
`/private/tmp/claude-*`. The `/tmp` path in the `ssh` command is on
`nik-gpu`, not this machine, and is just scrot's ephemeral output before
it gets copied off — that's the existing documented pattern in this
project's README, not a violation of the local scratchpad policy.
4. Read the pulled screenshot with the Read tool to visually sanity-check
the end state (e.g. did an unexpected dialog appear, did navigation end
up somewhere unexpected, does the game look like it's mid-action rather
than settled).
5. Known environment gotcha (from this repo's `Handoff.md`): the game's
UI-hide/photo-mode toggle can be left on from a prior session, showing
full-screen character art with no icons/header. If the screenshot looks
like that, it's a client-side toggle, not a bug in this project — note
it, don't treat it as a task failure.
## Output
Report, in under ~150 words:
- which task ran and the exit code
- what the task's own log lines actually said (quote the key line(s), not
the full dump)
- pass/fail/ambiguous, and why
- the scratchpad screenshot path, and anything notable visible in it
- if ambiguous or failed: the single most likely next diagnostic step
(e.g. "re-run and watch via Moonlight", "check AP/ticket balance first")

View File

@ -0,0 +1,99 @@
---
name: reference-parity-reviewer
description: Reviews a ba-auto-daily task module against its baas-reference counterpart to catch cases where reference logic that uses OCR, state detection, or a navigation state machine got replaced by a fixed coordinate, pixel probe, or randomized shortcut instead of being properly ported. Use after writing or changing any file under ba_auto/tasks/ (or ba_auto/navigation.py, ba_auto/detector.py in support of one), or whenever asked to check reference fidelity / OCR-policy compliance for a task module.
tools: Read, Grep, Glob, Bash
model: sonnet
---
You review one thing only: whether a `ba_auto/tasks/*.py` module actually
ports its `~/repo/baas-reference/module/...` counterpart's control flow, or
whether it quietly substituted something structurally simpler in place of
logic the reference drives from screen state.
## Why this agent exists
This project was bitten by exactly this failure mode once already. Before
Phase 10, `story_sweep.py` substituted ad hoc pixel probes, fixed
coordinates, and randomized stage selection for the reference's actual
OCR-driven region/stage navigation. Live testing found, in order: a wrong
modal-open probe, an unverified button click that silently under-spent AP,
a modal that doesn't close on Escape, and a latent hazard where a mistimed
keypress could have started a real battle. All four traced back to the same
root cause — the port hadn't actually reproduced the reference's
state-dependent decision-making, it had approximated it. That's the bug
class you're looking for.
## What counts as a real finding
A finding is: the reference reads screen state (OCR text, template/image
match, a per-cell color/pixel scan) **to decide what to do or where to
click**, and the local port replaced that decision with something that
doesn't actually make the same decision:
- a fixed coordinate substituted for a reference lookup whose whole point
was that the target isn't at a fixed coordinate
- a random or first-available choice substituted for a reference selection
the user configures or the reference computes
- an unverified click/keypress where the reference's flow gates the next
step on confirming the previous one landed
- a close/dismiss action assumed to work (e.g. "Escape closes this") that
was never actually checked against how the reference closes the
equivalent dialog
## What is NOT a finding (read `ba_auto/reference_notes/mapping.md` before
## flagging anything — it documents several intentional exceptions)
Not every fixed-position or non-OCR choice is a violation. This repo has
legitimate cases where a simpler local mechanism is the *faithful* port,
not an avoidance shortcut:
- **Shop item targeting** (`shop_common.py`/`shop_tactical.py`) uses a
fixed grid position + price-OCR-verify instead of the reference's
`get_item_position`, because that reference function indexes an
*external static price table* this repo was never given — porting it
faithfully means a locally-calibrated position table, not OCR-avoidance.
- **Tactical shop tab navigation** uses a fixed click instead of the
reference's OCR swipe-search, because this account's tab list is short
enough to fit on screen with nothing to search over — confirmed live.
- **Lesson region navigation** uses direct index-based clicking instead of
the reference's paged-arrow OCR nav, because this client renders regions
as a scrollable list that only ever settles at two scroll positions —
there is nothing to OCR-locate.
The test is always: *does the reference's original mechanism exist to
handle variability that is actually present in this local setup, or was
that variability designed away for a documented, verified reason?* If a
task module's comments/mapping.md row don't already document why a
shortcut is safe, treat it as unverified and ask for evidence, not as
automatically wrong.
## Procedure
1. Identify the target task file. If you weren't given one explicitly,
use `git status`/`git diff` to find the most recently changed file(s)
under `ba_auto/tasks/`.
2. Read `ba_auto/reference_notes/mapping.md` to find that feature's row —
it names the reference file and should explain any intentional
deviations (like the shop/lesson examples above).
3. Read the full reference file (and any shared helper it calls into,
e.g. `module/shop/shop_utils.py`) under `~/repo/baas-reference/module/`.
Never edit anything there — it's read-only.
4. Read the full local task file, plus any `ba_auto/navigation.py` /
`ba_auto/detector.py` helpers it calls.
5. Walk the reference's control flow step by step and match each
state-dependent decision point to what the local code actually does at
the equivalent point.
6. For each mismatch that isn't already justified by mapping.md or a
code comment, write a finding: reference file/function, local
file/line, what reference does vs. what local code does, a concrete
failure scenario (what real input/state makes this misfire), and
severity (silent under-spend / wrong click / hazard vs. cosmetic).
## Output
A short list of findings (empty list if none survive scrutiny), most
severe first. For each: one line naming the mismatch, one line on the
concrete failure scenario, and a suggested direction (usually "port
reference's X" rather than a full fix). Do not pad the report with
restating things that already check out — silence on a section means it
matched.

27
.claude/settings.json Normal file
View File

@ -0,0 +1,27 @@
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "f=$(command -v jq >/dev/null 2>&1 && jq -r '.tool_input.file_path // empty' || echo \"\"); case \"$f\" in *.py) python3 -m py_compile \"$f\" ;; */ba_dailies.sh|ba_dailies.sh) bash -n \"$f\" ;; *) : ;; esac",
"statusMessage": "Syntax-checking edited file..."
}
]
}
],
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "f=$(command -v jq >/dev/null 2>&1 && jq -r '.tool_input.file_path // empty' || echo \"\"); case \"$f\" in /tmp/*|/private/tmp/*) echo \"Blocked: use this repo's scratchpad/ directory instead of /tmp or /private/tmp (see CLAUDE.md's scratchpad policy).\" >&2; exit 2 ;; esac"
}
]
}
]
}
}

View File

@ -0,0 +1,67 @@
---
name: deploy-gpu
description: Sync this repo's working tree to nik-gpu, refresh the flat runtime paths via setup.sh, and optionally smoke-test one task against the live game. Use when the user asks to deploy, push, sync, or ship code to nik-gpu, or wants to try a task live after an edit.
disable-model-invocation: true
---
# /deploy-gpu
Deploys the current working tree to `nik-gpu` and refreshes the runtime paths
`~/ba_dailies.sh` actually executes from. See `README.md`'s "Deploying your
changes" section and `CLAUDE.md`'s "Deployment model" for the source of truth
this skill wraps — if either has changed, follow the doc over this file.
Optional task argument (smoke test): $ARGUMENTS
## Why two steps, not just rsync
`rsync` only updates the git checkout at `nik-gpu:~/repo/ba-auto-daily/`. The
launcher itself runs from separate flat paths (`~/ba_dailies.sh`,
`~/ba_daily.py`, `~/ba_auto/`, `~/ba_assets/`) that only `setup.sh` copies
into. Skipping the `setup.sh` step means nik-gpu keeps executing old code
even though the rsync "succeeded".
## Steps
1. From the repo root, run:
```bash
rsync -av --delete \
--exclude='.git/' \
--exclude='__pycache__/' \
--exclude='*.pyc' \
--exclude='.claude/settings.local.json' \
--exclude='graphify-out/' \
--exclude='screenshots/' \
--exclude='scratchpad/' \
./ nik-gpu:~/repo/ba-auto-daily/
```
2. Run:
```bash
ssh nik-gpu "cd ~/repo/ba-auto-daily && ./setup.sh"
```
Report any failure here directly (missing host tool, venv/pip error,
missing `tesseract`) rather than treating the deploy as done.
3. **Only if a task name was given in `$ARGUMENTS`** (e.g. `/deploy-gpu
cafe`), run it live as a smoke test:
```bash
ssh nik-gpu "~/ba_dailies.sh <task>"
```
Do not invent a task to run if none was given — several tasks
(`story_sweep`, `shop_common`, `shop_tactical`, `lesson`) spend real AP,
credits, tactical coin, or lesson tickets, and running one without the
user asking for that specific task would be an unrequested spend.
Exit code 0 only means the script ran without a Python exception, not
that the in-game action succeeded — surface the task's own log lines
(e.g. `claiming all`, `nothing to claim`, `patted N sparkle(s)`) to the
user, not just the exit code.
4. Summarize what happened: deploy succeeded/failed, and if a task was
smoke-tested, what its log output actually said.

View File

@ -0,0 +1,91 @@
---
name: new-task
description: Scaffold a new ba-auto-daily feature task following this repo's reference-first workflow — inspect the matching ~/repo/baas-reference/module/... file, update ba_auto/reference_notes/mapping.md, and stub ba_auto/tasks/<feature>.py. Use before writing any new task module, or when the user asks to start/port/implement a new feature (group, bounty, commission, arena, or any other daily task not yet migrated).
---
# /new-task
Scaffolds the start of a new feature task, per `CLAUDE.md`'s "Feature
implementation workflow". This is the mechanical setup only (steps 1-6 below)
— it does not write the actual click/detection logic, because that requires
reading the specific reference module and making per-feature judgment calls
the way `mailbox.py`, `cafe.py`, `story_sweep.py`, `shop_*.py`, and
`lesson.py` each did.
Feature name (and optional reference file hint): $ARGUMENTS
## Known backlog features (from `ba_auto/reference_notes/mapping.md`)
| Feature | Reference file |
|---|---|
| group / club | `module/group.py` |
| bounty | `module/rewarded_task.py` |
| commission(s) | `module/clear_special_task_power.py` |
| arena | `module/arena.py` |
If the requested feature isn't one of these, search
`~/repo/baas-reference/module/` (including subdirectories, e.g.
`module/explore_tasks/`, `module/shop/`) for the closest-matching file by
name/content before asking the user to confirm which reference file applies.
Never invent a Bash click sequence in place of a missing reference file —
if nothing matches, say so rather than guessing.
## Steps
1. **Read the reference module.** Open the matching
`~/repo/baas-reference/module/...` file (read-only — never edit it) and
identify:
- the main class/function driving control flow
- what it uses for state detection (OCR, color probe, template match,
UI-object query)
- what it uses for retries/failure handling
- which parts are Android/uiautomator2/ADB-specific and need a local
desktop replacement (`xdotool`/`scrot`/OpenCV/OCR via
`ba_auto/driver.py` / `ba_auto/detector.py`)
- which parts can be ported as direct Python control flow
2. **Summarize that flow** back to the user in a few sentences before
writing any code — this is the point where a wrong assumption is
cheapest to catch.
3. **Update `ba_auto/reference_notes/mapping.md`.** If a row already exists
for this feature (the four backlog rows above already have placeholder
rows with "Need to inspect" / "Not started"), fill in the real
"Reference functions/classes" and "Backend replacements" columns from
step 1 and set Status to `In progress`. If no row exists, add one in the
same table format.
4. **Check for missing driver/detector primitives.** Compare what step 1
needs against what already exists in `ba_auto/driver.py` and
`ba_auto/detector.py`. Call out anything missing before stubbing the
task — e.g. a new OCR crop helper, a new template-match wrapper.
5. **Create `ba_auto/tasks/<feature>.py`** with a minimal stub:
```python
from ba_auto import driver, detector, navigation, config
def run(driver, config):
"""<one-line pointer to the reference file/functions this ports>"""
raise NotImplementedError("port <module/xxx.py>'s <function> here")
```
Do not fill in the real logic here — that's the next, non-mechanical
step, and it should follow the reference's actual control flow rather
than a placeholder guess.
6. **Remind the user of what's still manual**, matching the rest of
`CLAUDE.md`'s workflow:
- implement the real logic in the stub, following the reference control
flow (state detection, retries, and all)
- add CLI dispatch for the new task in `ba_daily.py`
- decide whether it belongs in `DEFAULT_ORDER` — only free/reclaim-type
tasks do; anything that spends AP/credits/tickets/coin must stay
opt-in, matching `story_sweep`/`shop_common`/`shop_tactical`/`lesson`
- run the local syntax checks (`bash -n`, `python3 -m py_compile`) —
covered automatically now by this repo's `PostToolUse` hook, but worth
running explicitly before deploying
- deploy via `/deploy-gpu` and test against the live game
- update `plan.md` with a phase writeup once it's actually working,
including any real bugs found live — not just "done"