Refactor agent swarm config and expand work_queue status machine

- Expand `work_queue` status pipeline: `needs_translation` -> `in_progress` -> `translated` -> `wired` -> `verified`.
- Add `reset`, `wire`, and `verify` commands to `work_queue`.
- Add safety guard to `work_queue place`: prevents overwriting output file if scratch path equals output path.
- Update `build_index` to only mark labels as verified/translated if `label_exists_in_file` succeeds (greps `^LabelName:`).
- Fix engine/ prefix paths in `build_index` `TRANSLATED_MAP`.
- Auto-migrate DB on connection: `complete` -> `translated`, `unverified` -> `needs_translation`.
- Restructure agent files into scoped roles (`.agents/roles/`) and skills (`.agents/skills/`).
- Trim `.agents/AGENTS.md` to ~100 lines containing only overview, rules, quick-ref, and role table.
- Consolidate agent sandbox configurations into `.agents/settings.json` and empty `.agents/hooks.json`.
This commit is contained in:
Happyarch
2026-06-22 02:18:20 -04:00
parent 9d749a34bc
commit b5bf767c41
15 changed files with 874 additions and 494 deletions

View File

@@ -1,323 +1,89 @@
# Antigravity Pokemon Yellow 32-bit PMODE Port — Agent Swarm
# Antigravity Pokemon Yellow DOS Port — Agent Swarm
## Overview
Claude is **not** part of this swarm. Claude is used directly via Claude Code 1:1
for complex work, architecture decisions, and callsite wiring. The swarm handles
bulk translation of simple-category functions and placing them into the correct
files — nothing more.
Claude Code handles architecture, complex functions, and live-graph wiring.
The swarm handles bulk translation of `simple`-category functions only.
```
Dispatch_Manager (gemini-3.1-pro, effort:high) ← top-level coordinator
├── Code_Worker_1..5 (gemini-3.5-flash, effort:high)
├── Integration_Agent (gemini-3.5-flash, effort:high)
└── Docs_Commit_Agent (gemini-3.1-pro, effort:high)
Dispatch_Manager (gemini-3.1-pro) ← coordinator
├── Code_Worker_1..5 (gemini-3.5-flash)
├── Integration_Agent (gemini-3.5-flash)
└── Docs_Commit_Agent (gemini-3.1-pro)
```
**Hard boundary**: The swarm places translated code in the right files. It does
**not** wire functions into the game loop. Callsite connections (adding `call`
instructions into existing engine code, modifying `OverworldLoop`, etc.) are
reserved for Claude Code sessions where the user has direct control.
**Hard boundary**: Swarm places translated code. It does **not** wire functions
into the live game loop. Live-graph connections (`OverworldLoop`, `EnterMap`,
`DelayFrame` callees) are Claude Code only.
---
## Required Reading (all agents that write or review NASM)
## Role Files (read your role file before doing anything)
Every agent that generates, reviews, or integrates NASM code **must** read these
before doing any work. They are not optional background — violations will cause
silent runtime bugs or corrupt glitch behaviour.
| Document | Why it matters |
| Role | File |
|---|---|
| `CLAUDE.md` | Register map, memory model, DPMI gotchas, build conventions |
| `docs/register_map.md` | Canonical SM83→x86 register assignments |
| `docs/386_optimization_strategy.md` | Instruction selection rules for 386 targets |
| `docs/bugs_and_glitches.md` | Which SM83 bugs to preserve and at what fix level |
| `docs/glitch_safety.md` | Which glitches are safe to emulate vs. dangerous under DPMI |
Dispatch_Manager must read all five before writing any ticket.
Code Workers must read all five before translating any function.
Integration_Agent must read `CLAUDE.md` and `docs/register_map.md` before placing files.
Docs_Commit_Agent must read `CLAUDE.md` for commit conventions.
| Dispatch_Manager | `.agents/roles/dispatch.md` |
| Code_Worker | `.agents/roles/worker.md` |
| Integration_Agent | `.agents/roles/integration.md` |
| Docs_Commit_Agent | `.agents/roles/docs.md` |
---
## Role: Dispatch_Manager
- **Model**: `gemini-3.1-pro`
- **Settings**: `effort: high`
- **Objective**: Top-level swarm coordinator. Reads the work queue, writes
per-function tickets for Code Workers, reviews returned translations, marks
jobs complete, and hands finished files to the Integration and Docs agents.
Does **not** touch `complex`-category functions — those are left for Claude.
## Skills (load on demand with `agy skill <name>`)
### Work Queue Interface
Queue lives in `dos_port/tools/translation.db`. Use `dos_port/tools/work_queue`
(executable, no extension, outputs JSON):
| Skill | When to load |
|---|---|
| `register-map` | Before writing any NASM |
| `path-map` | Before placing any file |
| `386-checklist` | When choosing instructions |
| `bug-check` | When BUG/GLITCH annotation needed |
| `glitch-escalation` | When hitting a $FF__ register |
| `commit-format` | Before committing |
---
## Work Queue Quick Reference
```sh
dos_port/tools/work_queue status
dos_port/tools/work_queue claim --agent Dispatch_Manager --count 5 --category simple
dos_port/tools/work_queue fail --id <ID> --notes "reason"
dos_port/tools/work_queue list --category simple --status needs_translation --limit 20
dos_port/tools/work_queue pending-placement # jobs ready for Integration Agent
dos_port/tools/work_queue claim --agent <NAME> --count 5 --category simple
dos_port/tools/work_queue pending-placement
dos_port/tools/work_queue list --status needs_translation --category simple --limit 20
```
See `dos_port/tools/work_queue --help` for the full reference.
Run `dos_port/tools/work_queue --help` for full command reference.
### Scratch pad
Workers write their output to `dos_port/scratch/` — a fully untracked ephemeral
directory (gitignored). Each file is named `<id>__<label>.asm` and must begin
with a manifest header (see Code Worker section below). The Dispatch Manager
verifies the scratch file assembles, then calls:
Status pipeline: `needs_translation → in_progress → translated → [wired → verified]`
```sh
dos_port/tools/work_queue complete --id <ID> --scratch dos_port/scratch/<id>__<label>.asm --agent Dispatch_Manager
```
The Integration Agent picks up everything in `pending-placement`, moves each
file into `dos_port/src/`, and calls `work_queue place` to record the final path.
### Ticket format (sent to each Code Worker)
Each ticket must include:
1. Pret source file path and the exact label to translate
2. Target output file path under `dos_port/src/`
3. Relevant rows from `docs/register_map.md` (copy them verbatim)
4. `gb_memmap.inc` constants used by this function, with hex values pre-resolved
5. Any `; BUG()` / `; GLITCH:` annotations from `docs/bugs_and_glitches.md`
6. The exact `nasm -f coff -o /dev/null <file>` command to verify assembly
7. The correct include lines to paste at the top of the file (see below)
Vague tickets are not acceptable. Workers are not smart enough to look things up.
### Include path rule (copy into every ticket verbatim)
NASM is invoked from the `dos_port/` directory with `-I include/ -I .`.
Include paths must use the **short bare name only** — no directory prefix:
```nasm
%include "gb_memmap.inc" ; correct
%include "gb_macros.inc" ; correct
%include "dos_port/include/gb_memmap.inc" ; WRONG — breaks the build
%include "include/gb_memmap.inc" ; WRONG — breaks the build
```
Paste this rule into every worker ticket so it is impossible to miss.
### Dispatch rules
- Never assign two workers to the same output file simultaneously.
- Only dispatch `simple`-category jobs. If a job turns out to require hardware
I/O, graphics, or audio, call `work_queue fail` and leave it for Claude.
- Maximum 5 Code Workers active at once.
- After a worker returns, verify the file assembles before calling `work_queue complete`.
`wired` and `verified` are Claude Code session transitions only.
---
## Role: Integration_Agent
- **Model**: `gemini-3.5-flash`
- **Settings**: `effort: high`
- **Objective**: Place translated functions into the correct files so the build
can see them. This means adding `%include` lines to the appropriate aggregator
(e.g. `dos_port/src/home.asm`, `dos_port/src/engine/battle/experience.asm`)
and adding object-file rules to the `dos_port/Makefile` where needed. That is
the full scope of this role.
## Category Definitions
### What this agent MUST NOT do
- **No live-graph wiring.** Do not edit any function that is already reachable
from the running game loop (`OverworldLoop`, `EnterMap`, `DelayFrame`, and
everything they transitively call). Adding a `call NewFunction` inside one of
those would cause untested code to execute during normal gameplay.
That boundary is enforced in Claude Code sessions only.
- Translated functions may freely call other translated functions — that is
expected and correct. The restriction is one-directional: live code must not
gain new calls into unwired code.
- Do not modify any already-wired file beyond adding a `%include` to an
aggregator or a Makefile rule.
- Do not touch `unverified`-status files. Only `complete`-status translations
may be placed.
- Do not delete scratch files; the Dispatch Manager owns them. They disappear
naturally since the whole `dos_port/scratch/` directory is gitignored.
- DO NOT WRITE PYTHON SCRIPTS TO EDIT FILES!!! YOU HAVE TOOLS FOR THIS!!!
**`simple`** → swarm handles: pure arithmetic, data lookup, flag set/clear,
inventory math, battle formulas, BCD, random numbers. No `$FF__` I/O.
### Integration checklist (per function)
1. Run `dos_port/tools/work_queue pending-placement` to see what's ready.
2. Read the manifest header at the top of the scratch file
(`dos_port/scratch/<id>__<label>.asm`) to determine the source origin.
3. Decide the correct `dos_port/src/` destination (mirror the pret source tree
under `dos_port/src/`). Fill in the `target` and `aggregator` fields in the
header if helpful for the audit trail.
4. Copy the file to its `dos_port/src/` destination. The scratch copy remains
until the swarm session ends.
5. Add a `%include` line in the appropriate aggregator file.
6. If the file is a new compilation unit, add a `$(OBJ)/name.o` rule to the
Makefile and append the object to `OBJS`.
7. Run `nasm -f coff -o /dev/null <aggregator>` — must pass.
8. Run `make -C dos_port` — must pass clean with no new warnings.
9. Call `dos_port/tools/work_queue place --id <ID> --output dos_port/src/<path>`.
10. Hand the diff to `Docs_Commit_Agent`.
**`complex`** → Claude Code only: anything touching PPU, VGA, OAM, tile cache,
audio, joypad, menus with tile rendering, map transitions, pikachu, link cable.
---
## Role: Docs_Commit_Agent
- **Model**: `gemini-3.5-flash`
- **Settings**: `effort: high`
- **Objective**: Maintain documentation and execute git commits.
- Append a structured entry to `docs/translation_log.md` for every newly-placed
translation, using the pre-formatted entry from the queue tool (see below).
- Write a concise git commit message (see existing commit history for style).
- Stage exactly the files changed — never `git add -A`.
- Execute `git commit`. Does not push.
## Swarm Rules
### Writing translation log entries
For each function Integration Agent just placed, run:
```sh
dos_port/tools/work_queue translation-log-entry --id <ID>
```
This returns JSON with an `entry` field: a ready-to-append Markdown block
populated from the worker's notes (registers used, H-flag involvement, bug tags,
free-text notes). Append the `entry` string verbatim to `docs/translation_log.md`.
If a worker left notes blank, fill in what you can infer from the diff before
committing.
### Commit message conventions
- Subject ≤ 72 chars.
- Body: SM83 → x86 translation notes (register decisions, bug-fix level used).
- Trailer: `Co-Authored-By: Gemini <noreply@google.com>` for swarm-generated content.
- Never commit `.o`, `.orig`, `DUMP.BIN`, `FRAME.BIN`, or `translation.db`.
---
## Role: Code_Worker (×5 instances)
- **Model**: `gemini-3.5-flash`
- **Settings**: `effort: high`
- **Objective**: Translate one SM83 function to x86 NASM 32-bit protected mode
per the ticket from Dispatch_Manager. One ticket per worker at a time.
### Scratch file format
Every worker output must go to `dos_port/scratch/<id>__<label>.asm`.
Get the pre-formatted manifest header by running:
```sh
dos_port/tools/work_queue manifest --id <ID>
```
Write that header verbatim at the top of the file, **fill in the four WORKER
NOTES fields**, then append the translated NASM code beneath the `CODE BELOW`
line. The header looks like:
```nasm
; ╔══════════════════════════════════════════════════════════╗
; ║ PKMNDOS TRANSLATION MANIFEST ║
; ╚══════════════════════════════════════════════════════════╝
; queue_id : 1234
; label : CalcExperience
; source : engine/battle/experience.asm
; category : simple
; scratch : dos_port/scratch/1234__CalcExperience.asm
; -----------------------------------------------------------
; target : (Integration Agent fills this in)
; aggregator : (Integration Agent fills this in)
; -----------------------------------------------------------
; WORKER NOTES — fill in before calling work_queue complete
; registers : HL→ESI for exp table ptr, A→AL, BC→BX for growth rate
; hflag : not involved
; bug_tags : none
; notes : used imul for exp formula; SM83 used 16-bit mul via DE pair
; ╔══════════════════════════════════════════════════════════╗
; ║ CODE BELOW — do not modify the header above ║
; ╚══════════════════════════════════════════════════════════╝
CalcExperience:
...
```
`work_queue complete` automatically parses the four notes fields from the header
and stores them in the DB. Leave a field as its placeholder text (in parentheses)
if it genuinely does not apply — the parser ignores unfilled placeholders.
### Mandatory checklist
1. Read the exact pret source label from the ticket. Read surrounding context.
2. Check `docs/bugs_and_glitches.md` for any entry matching this function.
If found, apply the appropriate `; BUG(level):` block at the affected site.
3. Check `docs/glitch_safety.md` — if the function is involved in a known
glitch, verify the glitch is safe to emulate under DPMI before translating.
Emit `; GLITCH: <name> — Safety: <verdict>` at the relevant site.
4. Run `dos_port/tools/work_queue manifest --id <ID>` and write the header to
`dos_port/scratch/<id>__<label>.asm`.
5. Translate beneath the header using the register mapping from the ticket.
Use `[EBP + constant]` for all GB memory. Emit `; TODO-HW:` for I/O hits.
6. Fill in the four WORKER NOTES fields in the header before finishing.
7. Run `nasm -f coff -o /dev/null dos_port/scratch/<id>__<label>.asm` — must
assemble clean.
8. Return the scratch path and nasm stdout to Dispatch_Manager.
### Hard limits
- No spawning sub-agents.
- No touching graphics, VGA, OAM, VRAM, audio, or joypad code.
- Write only to `dos_port/scratch/` — do not modify any existing file.
- Do not add `%include` lines or Makefile rules — that is Integration Agent's job.
- `call` instructions inside the translated function are expected and correct.
Do not add `call` to any *existing* file outside the scratch output.
- `%include` lines must use bare filenames only: `%include "gb_memmap.inc"`.
Never write `%include "dos_port/include/gb_memmap.inc"` or any path prefix —
NASM is invoked from `dos_port/` with `-I include/` so the prefix breaks assembly.
- If the function touches a hardware register (`$FF__`) not in the ticket, stop
and report back immediately. Do not guess.
---
## Work Queue — Category Definitions
### `simple` → swarm handles
Pure arithmetic, data manipulation, flag operations. No `$FF__` register
accesses, no tile rendering, no sprite blitting, no sound.
Examples: battle damage/EXP formulas, PP decrement, random number generation,
BCD arithmetic, inventory management, status flag set/clear, item data lookups,
trainer-sight geometry, wild encounter rate math.
### `complex` → Claude Code 1:1 only
Anything touching hardware-mapped registers, the software PPU, the VGA blitter,
OAM shadow buffer, tile cache, audio engine, or central game-loop dispatch.
Also: menus with tile rendering, map transitions, sprite animation, pikachu
follow/PCM, link cable, printer, and battle cutscenes.
Examples: LCD control, vblank, palette loading, OAM DMA, tilemap updates, text
rendering, window layer, overworld map loading, battle animations, movie sequences.
---
## Swarm Rules & Safeguards
1. **No Claude in the loop.** The swarm does not spawn Claude agents. Claude
operates independently via Claude Code.
2. **Live-graph boundary is inviolable.** No agent may edit a function that is
already reachable from the running game loop to make it call newly-placed
code. Translated functions calling each other is fine — the restriction is
that live, tested code must not gain new edges into untested translations.
That wiring happens in Claude Code sessions.
3. **No direct DB access.** Never run `sqlite3` or any raw SQL against
`translation.db`. The table is named `functions`, not `queue` — direct SQL
will silently target the wrong table or bypass the audit log entirely.
Every queue mutation must go through `dos_port/tools/work_queue`. The correct
command for escalating a misclassified job is:
`work_queue recategorize --id <ID> --category complex --notes "reason"`
4. **No parallel file edits.** Dispatch must not assign two workers to the same
output file. Parallelism is at the file level only.
5. **Work queue is the source of truth.** Never mark complete outside
`work_queue complete`. Never place a function that is not `status=complete`.
6. **Unverified = blocked.** Files with `status=unverified` require a Claude
review before they can be placed. The swarm does not touch them.
7. **No `git add -A`.** Stage only files changed by the current work unit.
8. **No `--no-verify`.** Never skip pre-commit hooks.
9. **Required reading is mandatory.** See the Required Reading table near the top
of this file. All five documents must be read before any NASM is written.
Ignorance of a bug or glitch safety ruling is not an acceptable excuse.
10. **Hardware escalation.** If a `simple` ticket hits a `$FF__` register or
calls a graphics/audio routine, call `work_queue recategorize --category complex`
then `work_queue fail`, and leave it for Claude.
11. **Strict Command Invocation.** Whenever you execute a custom script, you MUST use the exact bare path (e.g. `dos_port/tools/work_queue`). NEVER prefix it with `./` or `/bin/bash` or `sh`. Do not vary this permutation under any circumstances.
1. **No Claude in the loop.** Swarm does not spawn Claude agents.
2. **Live-graph boundary is inviolable.** No new edges into untested code from
live-game-loop functions. Translated functions calling each other is fine.
3. **No direct DB access.** Never run `sqlite3` on `translation.db`.
Use `dos_port/tools/work_queue` exclusively.
4. **No parallel file edits.** One worker per output file at a time.
5. **Work queue is source of truth.** Never mark translated outside the tool.
6. **No `git add -A`.** Stage only files changed by the current work unit.
7. **No `--no-verify`.** Never skip pre-commit hooks.
8. **Hardware escalation.** `$FF__` hit on a simple job → `recategorize complex` + `fail`.
9. **Strict command invocation.** Use exact bare path `dos_port/tools/work_queue`.
Never prefix with `./`, `/bin/bash`, or `sh`.
10. **`complete` requires status=translated.** `place` requires `output ≠ scratch`.
---
@@ -328,14 +94,9 @@ dos_port/tools/build_index # non-destructive, adds only new rows
dos_port/tools/build_index --rebuild # full reset (clears manual status changes)
```
Quick queue checks:
```sh
dos_port/tools/work_queue list --category simple --status needs_translation --limit 20
dos_port/tools/work_queue list --category complex --status needs_translation --limit 20
dos_port/tools/work_queue status
```
---
## Agent Invocation Workaround
**IMPORTANT**: Due to issues with dynamic subagent registration, do not use `define_subagent` or attempt to invoke agents by their proper names (e.g. `Integration_Agent`). Instead, when you invoke any of the swarm agents (`Code_Worker`, `Integration_Agent`, `Docs_Commit_Agent`), you MUST use `TypeName: self` in your `invoke_subagent` call and supply the entire system prompt and ticket instructions directly in the `Prompt` field. This ensures the dispatch actually works.
## Agent Invocation Note
Due to dynamic subagent registration, use `TypeName: self` in `invoke_subagent`
and supply the full system prompt + ticket in the `Prompt` field.

View File

@@ -1,23 +1 @@
{
"antigravity.security.reviewPolicy": "proceed-in-sandbox",
"enableTerminalSandbox": true,
"antigravity.security.strictMode": false,
"permissions": {
"allow": [
"command(*)",
"function(*)"
],
"ask": [],
"deny": [
"command(dd *)",
"command(dd)",
"command(git push *)",
"command(git push)"
]
},
"sandbox.trustedBinaries": [
"nasm",
"make",
"dos_port/tools/work_queue"
]
}
{}

83
.agents/roles/dispatch.md Normal file
View File

@@ -0,0 +1,83 @@
# Role: Dispatch_Manager
**Model**: `gemini-3.1-pro` | **Settings**: `effort: high`
Top-level swarm coordinator. Reads the work queue, writes per-function tickets
for Code Workers, verifies returned translations, marks jobs translated, and
hands finished files to Integration and Docs agents.
Does **not** touch `complex`-category functions — those are left for Claude.
---
## Required Reading
Read these before writing any ticket:
| File | Why |
|---|---|
| `CLAUDE.md` | Register map, EBP memory model, DPMI gotchas, build conventions |
| `docs/register_map.md` | Canonical SM83→x86 register assignments |
| `docs/386_optimization_strategy.md` | Instruction selection rules for 386 targets |
| `docs/bugs_and_glitches.md` | Which SM83 bugs to preserve and at what fix level |
| `docs/glitch_safety.md` | Safe vs. dangerous glitches under DPMI |
Or load on demand:
- `agy skill register-map` — compact SM83→x86 table
- `agy skill bug-check` — BUG_FIX_LEVEL template
- `agy skill glitch-escalation` — when to escalate
---
## Work Queue Interface
```sh
dos_port/tools/work_queue status
dos_port/tools/work_queue claim --agent Dispatch_Manager --count 5 --category simple
dos_port/tools/work_queue fail --id <ID> --notes "reason"
dos_port/tools/work_queue list --category simple --status needs_translation --limit 20
dos_port/tools/work_queue pending-placement
```
Run `dos_port/tools/work_queue --help` for full reference.
After a worker returns, verify the scratch file assembles:
```sh
nasm -f coff -o /dev/null dos_port/scratch/<id>__<label>.asm
```
Then call:
```sh
dos_port/tools/work_queue complete --id <ID> --scratch dos_port/scratch/<id>__<label>.asm --agent Dispatch_Manager
```
---
## Ticket Format (sent to each Code Worker)
Each ticket must include:
1. Pret source file path and the exact label to translate
2. Target output file under `dos_port/src/` (verbatim mirror of pret path)
3. Relevant rows from `docs/register_map.md` (copy verbatim)
4. `gb_memmap.inc` constants used, with hex values pre-resolved
5. Any `; BUG()` / `; GLITCH:` annotations from `docs/bugs_and_glitches.md`
6. Exact `nasm -f coff -o /dev/null <file>` command to verify assembly
7. Include lines to paste at the top (bare filenames only — see include rule)
**Include path rule** (copy into every ticket):
```nasm
%include "gb_memmap.inc" ; correct — NASM invoked from dos_port/ with -I include/
%include "gb_macros.inc" ; correct
%include "dos_port/include/gb_memmap.inc" ; WRONG — breaks the build
%include "include/gb_memmap.inc" ; WRONG — breaks the build
```
---
## Dispatch Rules
- Never assign two workers to the same output file simultaneously.
- Only dispatch `simple`-category jobs. On hardware I/O, call `work_queue fail`.
- Maximum 5 Code Workers active at once.
- Workers use `agy skill` on demand — do not bulk-paste all docs into tickets.
- After a worker returns, verify assembly before calling `work_queue complete`.

39
.agents/roles/docs.md Normal file
View File

@@ -0,0 +1,39 @@
# Role: Docs_Commit_Agent
**Model**: `gemini-3.1-pro` | **Settings**: `effort: high`
Maintain documentation and execute git commits after each Integration Agent
placement. Does not translate or place code.
---
## Required Reading
- `CLAUDE.md` — commit conventions, project phase context
- `agy skill commit-format` — commit message format
---
## Translation Log Entries
For each function Integration Agent just placed, run:
```sh
dos_port/tools/work_queue translation-log-entry --id <ID>
```
This returns JSON with a ready-to-append `entry` field. Append it verbatim to
`docs/translation_log.md`. If worker left notes blank, fill in what you can
infer from the diff before committing.
---
## Commit Conventions
- Subject ≤ 72 chars. Use `agy skill commit-format` for the trailer format.
- Body: SM83 → x86 translation notes (register decisions, bug-fix level used).
- Trailer: `Co-Authored-By: Gemini <noreply@google.com>` for swarm content.
- Never commit: `.o`, `.orig`, `DUMP.BIN`, `FRAME.BIN`, `translation.db`,
scratch files, or any file not changed by the current work unit.
- `git add <exact files>` — never `git add -A` or `git add .`.
- Never `--no-verify`.
- Never push.

View File

@@ -0,0 +1,71 @@
# Role: Integration_Agent
**Model**: `gemini-3.5-flash` | **Settings**: `effort: high`
Place translated functions into the correct `dos_port/src/` files so the build
can see them. Add `%include` lines to aggregators, add Makefile rules where
needed. That is the full scope of this role.
---
## Required Reading
Read before placing anything:
- `CLAUDE.md` — linker section rules, build conventions
- `docs/register_map.md` — register conventions (needed for diff review)
- `agy skill path-map` — correct/wrong path table (load this first)
---
## Path Mapping Rule (CRITICAL)
The `dos_port/src/` path mirrors the pret source path one-to-one:
**prepend `dos_port/src/` to the pret source path. Never rename, restructure,
or drop prefix segments.**
| Pret source | Correct dos_port/src/ path |
|---|---|
| `engine/battle/experience.asm` | `dos_port/src/engine/battle/experience.asm` |
| `engine/math/bcd.asm` | `dos_port/src/engine/math/bcd.asm` |
| `engine/pokemon/bills_pc.asm` | `dos_port/src/engine/pokemon/bills_pc.asm` |
| `engine/items/inventory.asm` | `dos_port/src/engine/items/inventory.asm` |
| `engine/slots/slot_machine.asm` | `dos_port/src/engine/slots/slot_machine.asm` |
| `home/math.asm` | `dos_port/src/home/math.asm` |
**WRONG — never do this:**
| Pret source | Wrong path |
|---|---|
| `engine/math/bcd.asm` | `dos_port/src/util/bcd.asm` |
| `engine/pokemon/bills_pc.asm` | `dos_port/src/pokemon/bills_pc.asm` |
| `engine/items/inventory.asm` | `dos_port/src/items/inventory.asm` |
| `engine/slots/slot_machine.asm` | `dos_port/src/slots/slot_machine.asm` |
The `engine/` prefix is **never** dropped. `engine/math/` is **never** renamed to
`util/`. If unsure, run `ls dos_port/src/` before writing.
---
## Integration Checklist (per function)
1. `dos_port/tools/work_queue pending-placement` — see what's ready.
2. Read the manifest header in the scratch file for source origin.
3. Derive destination: `dos_port/src/` + pret source path (path-map rule above).
4. Copy file to destination. Scratch remains until session end (gitignored).
5. Add `%include` in the appropriate aggregator file.
6. If new compilation unit, add `$(OBJ)/name.o` to Makefile and append to `OBJS`.
7. `nasm -f coff -o /dev/null <aggregator>` — must pass.
8. `make -C dos_port` — must pass clean with no new warnings.
9. `dos_port/tools/work_queue place --id <ID> --output dos_port/src/<path>`
10. Hand diff to `Docs_Commit_Agent`.
---
## What This Agent MUST NOT Do
- **No live-graph wiring.** Do not edit any function reachable from
`OverworldLoop`, `EnterMap`, `DelayFrame`, or their transitive callees to
add calls to newly-placed code. Wiring is Claude Code only.
- Do not touch `translated`-status files until `place` is called.
- Do not modify wired files beyond adding `%include` to an aggregator.
- **DO NOT WRITE PYTHON SCRIPTS TO EDIT FILES.** You have tools for this.

69
.agents/roles/worker.md Normal file
View File

@@ -0,0 +1,69 @@
# Role: Code_Worker (×5 instances)
**Model**: `gemini-3.5-flash` | **Settings**: `effort: high`
Translate one SM83 function to x86 NASM 32-bit protected mode per ticket from
Dispatch_Manager. One ticket per worker at a time. Write output to
`dos_port/scratch/<id>__<label>.asm` only — never touch existing files.
---
## Required Reading (on demand — use `agy skill`)
Load these only when you need them:
- `agy skill register-map` — SM83→x86 register table + EBP memory model
- `agy skill bug-check` — BUG_FIX_LEVEL template and usage
- `agy skill glitch-escalation` — when to stop and report a $FF__ hit
- `agy skill 386-checklist` — instruction selection checklist
Full references (read if ticket is ambiguous):
- `docs/register_map.md`, `docs/bugs_and_glitches.md`, `docs/glitch_safety.md`
---
## Scratch File Format
Get the manifest header:
```sh
dos_port/tools/work_queue manifest --id <ID>
```
Write that header verbatim at the top of `dos_port/scratch/<id>__<label>.asm`,
**fill in the four WORKER NOTES fields**, then append translated NASM beneath
the `CODE BELOW` line. Example filled notes:
```nasm
; registers : HL→ESI for exp table ptr, A→AL, BC→BX for growth rate
; hflag : not involved
; bug_tags : BUG(cosmetic): overflow in EXP display — pret ref: experience.asm:L42
; notes : used imul for exp formula; SM83 used 16-bit mul via DE pair
```
Leave unfilled placeholders (text in parentheses) if they genuinely don't apply.
`work_queue complete` parses these fields automatically.
---
## Mandatory Checklist
1. Read the exact pret source label from the ticket. Read surrounding context.
2. Check ticket for `; BUG()` annotations. Apply `; BUG(level):` block at site.
3. If function involves a known glitch, load `agy skill glitch-escalation`.
4. Run `dos_port/tools/work_queue manifest --id <ID>` and write the header.
5. Translate under the header. Use `[EBP + constant]` for all GB memory.
Emit `; TODO-HW:` for any `$FF__` register access.
6. Fill in the four WORKER NOTES fields before finishing.
7. Run `nasm -f coff -o /dev/null dos_port/scratch/<id>__<label>.asm` — must pass.
8. Return scratch path and nasm stdout to Dispatch_Manager.
---
## Hard Limits
- No spawning sub-agents.
- No touching graphics, VGA, OAM, VRAM, audio, or joypad code.
- Write only to `dos_port/scratch/` — do not modify any existing file.
- Do not add `%include` lines or Makefile rules (Integration Agent's job).
- `call` inside translated function = fine. `call` in an existing file = never.
- Include lines use bare names only: `%include "gb_memmap.inc"`.
- If you hit a `$FF__` register not in the ticket, stop and report immediately.

View File

@@ -1,23 +1,24 @@
{
"antigravity.security.reviewPolicy": "proceed-in-sandbox",
"enableTerminalSandbox": true,
"antigravity.security.strictMode": false,
"permissions": {
"allow": [
"command(grep)",
"command(grep_search)",
"command(nasm)",
"command(cat)",
"command(dos_port/tools/work_queue)",
"command(dos_port/tools/build_index)",
"command(make)",
"command(bash)",
"command(dos_port/tools/process_placements)",
"command(sed)",
"command(awk)",
"command(ls)",
"command(make compare)",
"command(git status)",
"command(git add)",
"command(git commit)",
"command(find)"
"command(*)",
"function(*)"
],
"ask": [],
"deny": [
"command(dd *)",
"command(dd)",
"command(git push *)",
"command(git push)"
]
}
},
"sandbox.trustedBinaries": [
"nasm",
"make",
"dos_port/tools/work_queue",
"dos_port/tools/build_index"
]
}

View File

@@ -0,0 +1,46 @@
# Skill: 386-checklist
Fast instruction selection checklist for 386+ NASM translations.
## Memory Access
- `[EBP + constant]` for ALL GB memory — never use raw GB addresses
- Use `movzx eax, byte [ebp + addr]` to load 8-bit value into 32-bit reg
- Use `movzx eax, word [ebp + addr]` for 16-bit
- For 16-bit GB HL register pair: use ESI for pointer, `movzx esi, word [...]`
## Arithmetic
- 8-bit add/sub: operate on AL/BL/etc., let carry propagate naturally
- 16-bit GB arithmetic (BC, DE, HL): use 16-bit x86 BX/DX/SI with `movzx` after
- Multiply: `imul eax, ecx, N` preferred over shift sequences for clarity
- Divide: `div` or `idiv` with zero-extension for unsigned GB divisions
## Flags
- Z flag: direct — `test al, al` / `cmp al, bl` works identically
- C flag: direct — `jc`/`jnc` works
- H flag: lazy — only compute `[hf_shadow]` if DAA or CPL follows
- N flag: implicit in instruction choice (SUB sets, ADD clears)
## Common Patterns
```nasm
; GB: ld a, [hl] → movzx eax, byte [ebp + esi]
; GB: ld [hl], a → mov [ebp + esi], al
; GB: inc hl → inc esi
; GB: ld hl, NN → mov esi, NN
; GB: push bc → push ebx
; GB: pop bc → pop ebx
; GB: call Label → call Label
; GB: ret → ret
; GB: ret z → jz .done (inline check before call)
; GB: jr nz, .label → jnz .label
```
## Forbidden
- Do not use `[ESP + N]` to access GB memory
- Do not use segment overrides (DS:, ES:) — flat model only
- Do not use `loop` instruction — it only tests CX (16-bit under 32-bit mode)
- Do not use FAR jumps/calls

View File

@@ -0,0 +1,47 @@
# Skill: bug-check
BUG_FIX_LEVEL convention for this port.
## Levels
- `BUG_FIX_LEVEL 0` — original buggy behavior (default / no flag)
- `BUG_FIX_LEVEL 1` — critical bugs only (`/FIXCRIT` flag on PKMN.EXE)
- `BUG_FIX_LEVEL 2` — all bugs including cosmetic (`/FIXALL` flag)
## Template
```nasm
; BUG(critical): <what goes wrong> — pret ref: <file>:<label>, bugs_and_glitches.md#L<N>
%if BUG_FIX_LEVEL >= 1
; corrected implementation
%else
; original buggy behavior (verbatim from SM83)
%endif
```
For cosmetic/non-critical bugs:
```nasm
; BUG(cosmetic): <what goes wrong> — pret ref: <file>:<label>
%if BUG_FIX_LEVEL >= 2
; corrected implementation
%else
; original behavior
%endif
```
## When to Apply
1. Check ticket for explicit `; BUG()` annotations — apply if present.
2. If unsure whether something is a bug, check `docs/bugs_and_glitches.md`.
3. If the function has NO known bugs, emit no BUG block.
4. Never invent BUG blocks for behavior not documented in bugs_and_glitches.md.
## Glitch Comment
For intentional exploitable glitches:
```nasm
; GLITCH: <name> — <one-line description>
; Safety: safe under DPMI (bounded) | unsafe on bare HW if ACE reachable
```
Do NOT use the `%if` block for glitches — glitches are always preserved.

View File

@@ -0,0 +1,44 @@
# Skill: commit-format
Git commit message conventions for swarm-generated translations.
## Format
```
Translate <Label>[, <Label2>...]: <one-line summary>
SM83→x86 notes:
- <register decisions, e.g. "HL→ESI for table ptr">
- <H-flag involvement, e.g. "H flag not used">
- <BUG_FIX_LEVEL applied, e.g. "BUG(cosmetic) block at RestorePPAmount">
- <any notable deviation from literal translation>
Co-Authored-By: Gemini <noreply@google.com>
```
## Rules
- Subject line ≤ 72 characters
- Use present tense: "Translate", not "Translated"
- Body SM83 notes are mandatory — do not omit
- Trailer is `Co-Authored-By: Gemini <noreply@google.com>` for all swarm commits
- Never commit: `.o`, `.orig`, `DUMP.BIN`, `FRAME.BIN`, `translation.db`,
scratch files, unrelated editor files
- Stage only the files changed by this work unit: `git add <exact paths>`
- Never `git add -A` or `git add .`
- Never `--no-verify`
- Never push
## Example
```
Translate CalcExperience, GainExperience: battle experience formulas
SM83→x86 notes:
- HL→ESI for exp table ptr, BC→BX for growth rate pair
- H flag not involved
- imul used for exp formula (SM83 used 16-bit mul via DE pair)
- BUG(cosmetic) block at exp overflow display (BUG_FIX_LEVEL >= 2)
Co-Authored-By: Gemini <noreply@google.com>
```

View File

@@ -0,0 +1,37 @@
# Skill: glitch-escalation
When to stop and escalate instead of translating.
## Escalation Triggers
Stop immediately and report to Dispatch_Manager if the function:
1. **Accesses a `$FF__` hardware register** not listed in your ticket.
These are I/O boundaries — see `CLAUDE.md` "Hardware I/O Boundary" section.
Do NOT guess what the register does. Report the register address.
2. **Calls a graphics or audio routine** not in your ticket (any function in
`home/audio.asm`, `home/lcd.asm`, `engine/gfx/`, etc.).
3. **Accesses `$FE00`** (OAM) or `$FF40$FF4B` (LCDC, palettes, DMA).
4. **Involves a glitch rated "unsafe"** in `docs/glitch_safety.md`.
Unsafe = can trigger ACE or write to arbitrary memory under DPMI.
## How to Escalate
```sh
dos_port/tools/work_queue recategorize --id <ID> --category complex --notes "reason: $FF4X access"
dos_port/tools/work_queue fail --id <ID> --notes "escalated to complex: <reason>"
```
Return to Dispatch_Manager with:
- The register or function that triggered escalation
- The line number in the pret source
## Safe to Translate Without Escalation
- Any `$FF` address that is a GB RAM address (`$FF80$FFFE` = HRAM):
these are regular memory, not I/O. Map via `[EBP + hram_offset]`.
- `call` to another simple function listed in `AGENTS.md` simple category.
- Bit manipulation of GB RAM bytes (status flags, counters, etc.).

View File

@@ -0,0 +1,40 @@
# Skill: path-map
Correct/wrong path table for placing translated files.
## Rule
`dos_port/src/` output path = `dos_port/src/` + pret source path verbatim.
Never drop prefix segments. Never rename directories.
## Correct Examples
| Pret source | dos_port/src/ destination |
|---|---|
| `engine/battle/experience.asm` | `dos_port/src/engine/battle/experience.asm` |
| `engine/math/bcd.asm` | `dos_port/src/engine/math/bcd.asm` |
| `engine/math/multiply_divide.asm` | `dos_port/src/engine/math/multiply_divide.asm` |
| `engine/pokemon/bills_pc.asm` | `dos_port/src/engine/pokemon/bills_pc.asm` |
| `engine/items/inventory.asm` | `dos_port/src/engine/items/inventory.asm` |
| `engine/items/get_bag_item_quantity.asm` | `dos_port/src/engine/items/get_bag_item_quantity.asm` |
| `engine/slots/slot_machine.asm` | `dos_port/src/engine/slots/slot_machine.asm` |
| `engine/debug/debug_party.asm` | `dos_port/src/engine/debug/debug_party.asm` |
| `home/math.asm` | `dos_port/src/home/math.asm` |
| `home/copy2.asm` | `dos_port/src/home/copy2.asm` |
## Wrong (DO NOT USE)
| Pret source | Wrong path |
|---|---|
| `engine/math/bcd.asm` | `dos_port/src/util/bcd.asm` ← drops engine/, renames |
| `engine/math/multiply_divide.asm` | `dos_port/src/util/multiply_divide.asm` |
| `engine/pokemon/bills_pc.asm` | `dos_port/src/pokemon/bills_pc.asm` ← drops engine/ |
| `engine/items/inventory.asm` | `dos_port/src/items/inventory.asm` ← drops engine/ |
| `engine/slots/slot_machine.asm` | `dos_port/src/slots/slot_machine.asm` |
| `engine/debug/debug_party.asm` | `dos_port/src/debug/debug_party.asm` |
| `home/copy2.asm` | `dos_port/src/home/copy.asm` ← wrong filename |
## Exception
Manually-written Claude Code files may use different names. These are listed in
`dos_port/tools/build_index` in `VERIFIED_MAP` and are not governed by this rule.

View File

@@ -0,0 +1,39 @@
# Skill: register-map
SM83 → x86 register mapping for this port.
## Register Table
| SM83 | x86 | Notes |
|------|-----|-------|
| A | AL | Accumulator |
| F: Z, C | EFLAGS ZF, CF | Direct |
| F: H | `[hf_shadow]` | BSS byte; lazy — only update where DAA/CPL consume H |
| F: N | (implicit) | Tracked via instruction choice, not a flag |
| BC | BX | B = BH, C = BL |
| DE | DX | D = DH, E = DL |
| HL | ESI | Full 32-bit, used for flat addressing |
| SP | ESP | Direct; mind calling convention |
| — | EBP | Fixed base → emulated GB address space |
| — | EDI | Secondary pointer / blit destination |
| — | ECX | Loop counter / scratch |
## EBP Memory Model
`EBP` = base of ~96 KB DPMI allocation (64 KB GB space + extras).
All emulated GB memory: `[EBP + constant]` where constants come from
`dos_port/include/gb_memmap.inc`. Never use raw GB addresses — always offset
from EBP. Example:
```nasm
; GB: ld a, [wCurItem] → x86: mov al, [ebp + wCurItem]
; GB: ld [hl], a → x86: mov [ebp + esi], al
```
## Preferred 386 Instructions
- `movzx`/`movsx` for zero/sign extension (never use AND to zero-extend)
- `imul reg, reg, imm` for index math
- `lea` for flags-preserving address computation
- `rep stos`/`rep movs` for block fills/copies
- Never use 32-bit operands for 8-bit/16-bit GB values without masking

View File

@@ -1,10 +1,10 @@
#!/usr/bin/env python3
"""
build_index.py — Scan the pret/pokeyellow GB source (home/ and engine/) and
build_index — Scan the pret/pokeyellow GB source (home/ and engine/) and
build (or rebuild) the translation work-queue database at dos_port/tools/translation.db.
Usage:
python3 dos_port/tools/build_index.py [--rebuild]
dos_port/tools/build_index [--rebuild]
--rebuild Drop and recreate all tables before inserting. Without this flag,
only rows whose source_file is not already present are inserted,
@@ -18,18 +18,14 @@ import sqlite3
import sys
from datetime import datetime, timezone
# ── Repo root is two levels above this script ──────────────────────────────────
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
REPO_ROOT = os.path.normpath(os.path.join(SCRIPT_DIR, '..', '..'))
DB_PATH = os.path.join(SCRIPT_DIR, 'translation.db')
# ── Categorisation tables ───────────────────────────────────────────────────────
# Paths are relative to REPO_ROOT. Order: first match wins (checked against the
# full relative path of each .asm file using str.startswith / 'in' membership).
# Paths are relative to REPO_ROOT.
# Files whose ENTIRE CONTENT is simple (no HW I/O, no gfx, no audio).
SIMPLE_FILES = {
# home/ utility / data routines
'home/math.asm',
'home/compare.asm',
'home/copy.asm',
@@ -51,12 +47,10 @@ SIMPLE_FILES = {
'home/hidden_events.asm',
'home/print_bcd.asm',
'home/print_num.asm',
# engine/flag / math
'engine/flag_action.asm',
'engine/math/bcd.asm',
'engine/math/multiply_divide.asm',
'engine/math/random.asm',
# engine/battle — pure-logic routines only
'engine/battle/decrement_pp.asm',
'engine/battle/experience.asm',
'engine/battle/misc.asm',
@@ -67,7 +61,6 @@ SIMPLE_FILES = {
'engine/battle/save_trainer_name.asm',
'engine/battle/read_trainer_party.asm',
'engine/battle/get_trainer_name.asm',
# engine/battle/move_effects — all pure stat manipulation
'engine/battle/move_effects/conversion.asm',
'engine/battle/move_effects/drain_hp.asm',
'engine/battle/move_effects/focus_energy.asm',
@@ -82,14 +75,12 @@ SIMPLE_FILES = {
'engine/battle/move_effects/reflect_light_screen.asm',
'engine/battle/move_effects/substitute.asm',
'engine/battle/move_effects/transform.asm',
# engine/items — data lookup / arithmetic only
'engine/items/get_bag_item_quantity.asm',
'engine/items/inventory.asm',
'engine/items/subtract_paid_money.asm',
'engine/items/tm_prices.asm',
'engine/items/tms.asm',
'engine/items/tmhm.asm',
# engine/pokemon — data / formula
'engine/pokemon/evos_moves.asm',
'engine/pokemon/experience.asm',
'engine/pokemon/set_types.asm',
@@ -97,7 +88,6 @@ SIMPLE_FILES = {
'engine/pokemon/add_mon.asm',
'engine/pokemon/remove_mon.asm',
'engine/pokemon/load_mon_data.asm',
# engine/overworld — geometry / flag checks only
'engine/overworld/wild_mons.asm',
'engine/overworld/daycare_exp.asm',
'engine/overworld/sprite_collisions.asm',
@@ -105,78 +95,80 @@ SIMPLE_FILES = {
'engine/overworld/is_player_just_outside_map.asm',
'engine/overworld/clear_variables.asm',
'engine/overworld/specific_script_flags.asm',
# engine/events — give/heal/flag events (no gfx)
'engine/events/give_pokemon.asm',
'engine/events/heal_party.asm',
'engine/events/card_key.asm',
# engine/debug
'engine/debug/debug_menu.asm',
'engine/debug/debug_party.asm',
}
# Directory prefixes whose every file is simple
SIMPLE_PREFIXES = (
'engine/events/hidden_events/',
)
# ── Files whose translations are already in dos_port/src and were written by
# Claude (trusted, verified in DOSBox-X). Status = 'complete'.
COMPLETE_MAP = {
# pret source path → dos_port output path
'home/joypad.asm': 'dos_port/src/input/joypad.asm',
'home/load_font.asm': 'dos_port/src/gfx/load_font.asm',
'home/copy.asm': 'dos_port/src/util/copy_data.asm',
'home/init.asm': 'dos_port/src/init/init.asm',
'engine/movie/title_yellow.asm': 'dos_port/src/movie/title.asm',
'engine/gfx/sprite_oam.asm': 'dos_port/src/gfx/sprite_oam.asm',
'engine/overworld/movement.asm': 'dos_port/src/overworld/movement.asm',
# ── Files verified working in DOSBox-X (Claude Code sessions).
# Status = 'verified'. These may use non-mirrored paths where Claude deliberately
# chose a different name (e.g., home/copy.asm → src/util/copy_data.asm).
# Rule: label must exist in output file; if absent, falls back to needs_translation.
VERIFIED_MAP = {
# pret source → dos_port output (repo-relative)
'home/joypad.asm': 'dos_port/src/input/joypad.asm',
'home/load_font.asm': 'dos_port/src/gfx/load_font.asm',
'home/copy.asm': 'dos_port/src/util/copy_data.asm',
'home/init.asm': 'dos_port/src/init/init.asm',
'engine/movie/title_yellow.asm': 'dos_port/src/movie/title.asm',
'engine/gfx/sprite_oam.asm': 'dos_port/src/gfx/sprite_oam.asm',
'engine/overworld/movement.asm': 'dos_port/src/overworld/movement.asm',
}
# ── Files that exist in dos_port/src but were AI-generated and are unverified.
# Status = 'unverified'. The integration agent must review before wiring.
UNVERIFIED_MAP = {
'home/math.asm': 'dos_port/src/home/math.asm',
'home/compare.asm': 'dos_port/src/home/compare.asm',
'home/random.asm': 'dos_port/src/home/random.asm',
'home/copy2.asm': 'dos_port/src/home/copy.asm',
'home/array.asm': 'dos_port/src/home/array.asm',
'home/count_set_bits.asm': 'dos_port/src/home/count_set_bits.asm',
'engine/math/bcd.asm': 'dos_port/src/util/bcd.asm',
'engine/math/multiply_divide.asm': 'dos_port/src/util/multiply_divide.asm',
'engine/math/random.asm': 'dos_port/src/util/random.asm',
'engine/battle/decrement_pp.asm': 'dos_port/src/engine/battle/decrement_pp.asm',
'engine/battle/experience.asm': 'dos_port/src/engine/battle/experience.asm',
'engine/flag_action.asm': 'dos_port/src/engine/flag_action.asm',
'engine/items/get_bag_item_quantity.asm':'dos_port/src/items/get_bag_item_quantity.asm',
'engine/items/inventory.asm': 'dos_port/src/items/inventory.asm',
'engine/items/subtract_paid_money.asm': 'dos_port/src/items/subtract_paid_money.asm',
'engine/items/tm_prices.asm': 'dos_port/src/items/tm_prices.asm',
'engine/items/tms.asm': 'dos_port/src/engine/items/tms.asm',
'engine/items/tmhm.asm': 'dos_port/src/engine/items/tmhm.asm',
'engine/items/item_effects.asm': 'dos_port/src/engine/items/item_effects.asm',
'engine/items/itemfinder.asm': 'dos_port/src/items/itemfinder.asm',
'engine/items/super_rod.asm': 'dos_port/src/items/super_rod.asm',
'engine/items/town_map.asm': 'dos_port/src/engine/items/town_map.asm',
'engine/pokemon/evos_moves.asm': 'dos_port/src/engine/pokemon/evos_moves.asm',
'engine/pokemon/set_types.asm': 'dos_port/src/engine/pokemon/set_types.asm',
'engine/pokemon/status_ailments.asm': 'dos_port/src/engine/pokemon/status_ailments.asm',
'engine/pokemon/add_mon.asm': 'dos_port/src/engine/pokemon/add_mon.asm',
'engine/pokemon/remove_mon.asm': 'dos_port/src/pokemon/remove_mon.asm',
'engine/pokemon/load_mon_data.asm': 'dos_port/src/engine/pokemon/load_mon_data.asm',
'engine/pokemon/experience.asm': 'dos_port/src/pokemon/experience.asm',
'engine/pokemon/bills_pc.asm': 'dos_port/src/pokemon/bills_pc.asm',
'engine/menus/pc.asm': 'dos_port/src/engine/menus/pc.asm',
'engine/menus/save.asm': 'dos_port/src/engine/menus/save.asm',
'engine/menus/swap_items.asm': 'dos_port/src/engine/menus/swap_items.asm',
'engine/menus/text_box.asm': 'dos_port/src/engine/menus/text_box.asm',
'engine/pikachu/pikachu_status.asm': 'dos_port/src/engine/pikachu/pikachu_status.asm',
'engine/slots/slot_machine.asm': 'dos_port/src/slots/slot_machine.asm',
'engine/debug/debug_party.asm': 'dos_port/src/debug/debug_party.asm',
'engine/predefs.asm': 'dos_port/src/engine/predefs.asm',
# ── Files translated by the swarm, in dos_port/src/, not yet verified in DOSBox-X.
# Status = 'translated'. Path rule: always `dos_port/src/` + pret source path
# verbatim. Never drop engine/, never rename paths.
# Rule: label must exist in output file; if absent, falls back to needs_translation.
TRANSLATED_MAP = {
# pret source → dos_port output (repo-relative; one-to-one mirroring)
'home/math.asm': 'dos_port/src/home/math.asm',
'home/compare.asm': 'dos_port/src/home/compare.asm',
'home/random.asm': 'dos_port/src/home/random.asm',
'home/copy2.asm': 'dos_port/src/home/copy2.asm',
'home/array.asm': 'dos_port/src/home/array.asm',
'home/count_set_bits.asm': 'dos_port/src/home/count_set_bits.asm',
'engine/math/bcd.asm': 'dos_port/src/engine/math/bcd.asm',
'engine/math/multiply_divide.asm': 'dos_port/src/engine/math/multiply_divide.asm',
'engine/math/random.asm': 'dos_port/src/engine/math/random.asm',
'engine/battle/decrement_pp.asm': 'dos_port/src/engine/battle/decrement_pp.asm',
'engine/battle/experience.asm': 'dos_port/src/engine/battle/experience.asm',
'engine/flag_action.asm': 'dos_port/src/engine/flag_action.asm',
'engine/items/get_bag_item_quantity.asm': 'dos_port/src/engine/items/get_bag_item_quantity.asm',
'engine/items/inventory.asm': 'dos_port/src/engine/items/inventory.asm',
'engine/items/subtract_paid_money.asm': 'dos_port/src/engine/items/subtract_paid_money.asm',
'engine/items/tm_prices.asm': 'dos_port/src/engine/items/tm_prices.asm',
'engine/items/tms.asm': 'dos_port/src/engine/items/tms.asm',
'engine/items/tmhm.asm': 'dos_port/src/engine/items/tmhm.asm',
'engine/items/item_effects.asm': 'dos_port/src/engine/items/item_effects.asm',
'engine/items/itemfinder.asm': 'dos_port/src/engine/items/itemfinder.asm',
'engine/items/super_rod.asm': 'dos_port/src/engine/items/super_rod.asm',
'engine/items/town_map.asm': 'dos_port/src/engine/items/town_map.asm',
'engine/pokemon/evos_moves.asm': 'dos_port/src/engine/pokemon/evos_moves.asm',
'engine/pokemon/set_types.asm': 'dos_port/src/engine/pokemon/set_types.asm',
'engine/pokemon/status_ailments.asm': 'dos_port/src/engine/pokemon/status_ailments.asm',
'engine/pokemon/add_mon.asm': 'dos_port/src/engine/pokemon/add_mon.asm',
'engine/pokemon/remove_mon.asm': 'dos_port/src/engine/pokemon/remove_mon.asm',
'engine/pokemon/load_mon_data.asm': 'dos_port/src/engine/pokemon/load_mon_data.asm',
'engine/pokemon/experience.asm': 'dos_port/src/engine/pokemon/experience.asm',
'engine/pokemon/bills_pc.asm': 'dos_port/src/engine/pokemon/bills_pc.asm',
'engine/menus/pc.asm': 'dos_port/src/engine/menus/pc.asm',
'engine/menus/save.asm': 'dos_port/src/engine/menus/save.asm',
'engine/menus/swap_items.asm': 'dos_port/src/engine/menus/swap_items.asm',
'engine/menus/text_box.asm': 'dos_port/src/engine/menus/text_box.asm',
'engine/pikachu/pikachu_status.asm': 'dos_port/src/engine/pikachu/pikachu_status.asm',
'engine/slots/slot_machine.asm': 'dos_port/src/engine/slots/slot_machine.asm',
'engine/debug/debug_party.asm': 'dos_port/src/engine/debug/debug_party.asm',
'engine/predefs.asm': 'dos_port/src/engine/predefs.asm',
}
# ── Label extraction ────────────────────────────────────────────────────────────
# Match non-local, non-numeric labels at column 0 in RGBDS source.
# ── Label helpers ───────────────────────────────────────────────────────────────
LABEL_RE = re.compile(r'^([A-Za-z_][A-Za-z0-9_]*):{1,2}')
def extract_labels(filepath):
@@ -191,22 +183,39 @@ def extract_labels(filepath):
pass
return labels
def label_exists_in_file(label, filepath):
"""Return True if 'label:' or 'label::' appears at column 0 in filepath."""
full = os.path.join(REPO_ROOT, filepath)
if not os.path.exists(full):
return False
pattern = re.compile(r'^' + re.escape(label) + r':{1,2}', re.MULTILINE)
try:
with open(full, encoding='utf-8', errors='replace') as fh:
return bool(pattern.search(fh.read()))
except OSError:
return False
# ── Schema ──────────────────────────────────────────────────────────────────────
SCHEMA = """
CREATE TABLE IF NOT EXISTS functions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
source_file TEXT NOT NULL,
category TEXT NOT NULL CHECK(category IN ('simple','complex')),
status TEXT NOT NULL DEFAULT 'needs_translation'
CHECK(status IN
('needs_translation','in_progress',
'unverified','complete','skip')),
output_file TEXT,
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
source_file TEXT NOT NULL,
category TEXT NOT NULL CHECK(category IN ('simple','complex')),
status TEXT NOT NULL DEFAULT 'needs_translation'
CHECK(status IN (
'needs_translation','in_progress',
'translated','wired','verified','skip',
'complete','unverified'
)),
output_file TEXT,
scratch_file TEXT,
assigned_agent TEXT,
notes TEXT,
created_at TEXT DEFAULT (datetime('now','utc')),
updated_at TEXT DEFAULT (datetime('now','utc'))
notes TEXT,
translation_notes TEXT,
created_at TEXT DEFAULT (datetime('now','utc')),
updated_at TEXT DEFAULT (datetime('now','utc'))
);
CREATE INDEX IF NOT EXISTS idx_fn_status ON functions(status);
@@ -225,7 +234,6 @@ CREATE TABLE IF NOT EXISTS translation_log (
"""
def categorise(rel_path):
"""Return 'simple' or 'complex' for a repo-relative .asm path."""
norm = rel_path.replace('\\', '/')
if norm in SIMPLE_FILES:
return 'simple'
@@ -234,16 +242,27 @@ def categorise(rel_path):
return 'simple'
return 'complex'
def status_and_output(rel_path):
def status_and_output(rel_path, label):
"""Return (status, output_path) for this label.
For verified/translated maps: only set that status if the label actually
exists in the output file. If the file is missing or the label isn't found,
fall back to needs_translation so the DB reflects reality.
"""
norm = rel_path.replace('\\', '/')
if norm in COMPLETE_MAP:
return 'complete', COMPLETE_MAP[norm]
if norm in UNVERIFIED_MAP:
return 'unverified', UNVERIFIED_MAP[norm]
if norm in VERIFIED_MAP:
out = VERIFIED_MAP[norm]
if label_exists_in_file(label, out):
return 'verified', out
return 'needs_translation', None
if norm in TRANSLATED_MAP:
out = TRANSLATED_MAP[norm]
if label_exists_in_file(label, out):
return 'translated', out
return 'needs_translation', None
return 'needs_translation', None
def scan_source_files():
"""Yield (rel_path, [label, ...]) for every .asm in home/ and engine/."""
for top in ('home', 'engine'):
top_dir = os.path.join(REPO_ROOT, top)
if not os.path.isdir(top_dir):
@@ -265,14 +284,16 @@ def build(rebuild=False):
cur.executescript(SCHEMA)
# Migrate legacy status names from old schema
cur.execute("UPDATE functions SET status='translated' WHERE status='complete'")
cur.execute("UPDATE functions SET status='needs_translation' WHERE status='unverified'")
now = datetime.now(timezone.utc).isoformat()
inserted = skipped = 0
for rel_path, labels in scan_source_files():
cat = categorise(rel_path)
status, out_file = status_and_output(rel_path)
cat = categorise(rel_path)
# Skip files already in DB unless rebuilding
if not rebuild:
cur.execute("SELECT 1 FROM functions WHERE source_file=? LIMIT 1", (rel_path,))
if cur.fetchone():
@@ -280,10 +301,10 @@ def build(rebuild=False):
continue
if not labels:
# Index the file itself as a single entry so it isn't lost
labels = [os.path.splitext(os.path.basename(rel_path))[0]]
for label in labels:
status, out_file = status_and_output(rel_path, label)
cur.execute("""
INSERT INTO functions (name, source_file, category, status,
output_file, created_at, updated_at)

View File

@@ -7,6 +7,13 @@ All output is JSON so agents can parse it without screen-scraping.
** NEVER access translation.db with raw SQL. Use this tool exclusively. **
Direct sqlite3 calls bypass the audit log and will use the wrong table name.
Status pipeline
---------------
needs_translation → in_progress → translated → [wired → verified]
(swarm terminal)
wired and verified are Claude Code session transitions only.
Commands
--------
status Queue summary counts by category × status.
@@ -14,26 +21,25 @@ Commands
List matching functions.
claim --agent ID [--count N] [--category C]
Atomically claim N jobs (default 1).
Returns claimed rows + suggested scratch paths.
manifest --id ID Print the manifest header a worker must write
at the top of its scratch file.
manifest --id ID Print the manifest header a worker must write.
complete --id ID --scratch PATH [--agent ID]
Mark a function complete. Parses the worker
notes block from the scratch file and stores
it in the DB. Verifies the file exists first.
Worker marks job translated. Scratch file must
exist and be different from the output path.
place --id ID --output PATH [--agent ID]
Integration Agent records final dos_port/src/
placement and deletes the scratch file.
reset --id ID Return any function to needs_translation
(preserves category). Maintenance command.
wire --id ID [--agent ID] Claude Code: translated → wired.
verify --id ID [--agent ID] Claude Code: wired → verified.
recategorize --id ID --category C [--agent ID] [--notes MSG]
Move a job to a different category (e.g.
simple→complex when HW I/O is discovered).
Move a job to a different category.
Resets status to needs_translation. Logged.
unclaim --id ID Return a claimed job to needs_translation.
fail --id ID [--notes MSG] Return a job with a failure note.
log --id ID Full audit log for one function.
pending-placement List complete jobs waiting for Integration Agent.
translation-log-entry --id ID Emit a formatted docs/translation_log.md entry
for Docs_Commit_Agent to append to that file.
pending-placement List translated jobs waiting for Integration Agent.
translation-log-entry --id ID Emit a formatted docs/translation_log.md entry.
Exit codes: 0 = success, 1 = error (details in JSON 'error' key).
"""
@@ -46,12 +52,18 @@ import sqlite3
import sys
from datetime import datetime, timezone
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
REPO_ROOT = os.path.normpath(os.path.join(SCRIPT_DIR, '..', '..'))
DB_PATH = os.path.join(SCRIPT_DIR, 'translation.db')
SCRATCH_DIR = os.path.join(REPO_ROOT, 'dos_port', 'scratch')
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
REPO_ROOT = os.path.normpath(os.path.join(SCRIPT_DIR, '..', '..'))
DB_PATH = os.path.join(SCRIPT_DIR, 'translation.db')
SCRATCH_DIR = os.path.join(REPO_ROOT, 'dos_port', 'scratch')
# ── DB helpers ──────────────────────────────────────────────────────────────────
ALL_STATUSES = [
'needs_translation', 'in_progress', 'translated', 'wired', 'verified', 'skip',
# legacy values kept so old rows survive migration
'complete', 'unverified',
]
# ── DB helpers ───────────────────────────────────────────────────────────────────
def db():
if not os.path.exists(DB_PATH):
@@ -68,6 +80,13 @@ def _migrate(con):
con.execute("ALTER TABLE functions ADD COLUMN scratch_file TEXT")
if 'translation_notes' not in cols:
con.execute("ALTER TABLE functions ADD COLUMN translation_notes TEXT")
# Rename legacy statuses. Disable CHECK constraints temporarily because the
# old schema only allows 'complete'/'unverified'; new names are 'translated'
# and 'needs_translation'.
con.execute("PRAGMA ignore_check_constraints = ON")
con.execute("UPDATE functions SET status='translated' WHERE status='complete'")
con.execute("UPDATE functions SET status='needs_translation' WHERE status='unverified'")
con.execute("PRAGMA ignore_check_constraints = OFF")
con.commit()
def scratch_path(fid, label):
@@ -94,7 +113,7 @@ def log_transition(cur, function_id, old_status, new_status, agent=None, notes=N
VALUES (?, ?, ?, ?, ?, ?)
""", (function_id, old_status, new_status, agent, notes, now_utc()))
# ── Manifest / notes parsing ────────────────────────────────────────────────────
# ── Manifest / notes parsing ────────────────────────────────────────────────────
def make_manifest(row):
sp = scratch_path(row['id'], row['name'])
@@ -124,29 +143,25 @@ def make_manifest(row):
]
return '\n'.join(lines)
# Fields the worker fills in — parsed from the scratch file header.
_WORKER_FIELDS = ('registers', 'hflag', 'bug_tags', 'notes')
def parse_notes_from_scratch(path):
"""Read the manifest header from a scratch file and return a dict of worker notes."""
result = {f: '' for f in _WORKER_FIELDS}
try:
with open(path, encoding='utf-8', errors='replace') as fh:
for line in fh:
# Stop at the CODE BELOW sentinel
if 'CODE BELOW' in line:
break
m = re.match(r';\s*(registers|hflag|bug_tags|notes)\s*:\s*(.*)', line)
if m:
key, val = m.group(1), m.group(2).strip()
# Ignore unfilled placeholder values
if not (val.startswith('(') and val.endswith(')')):
result[key] = val
except OSError:
pass
return result
# ── sub-commands ────────────────────────────────────────────────────────────────
# ── sub-commands ────────────────────────────────────────────────────────────────
def cmd_status(_args):
con = db()
@@ -237,7 +252,7 @@ def cmd_manifest(args):
con.close()
def cmd_complete(args):
"""Worker calls this after writing the scratch file."""
"""Worker marks job translated; scratch file must exist and differ from output."""
con = db()
cur = con.cursor()
row = cur.execute("SELECT id, status, name FROM functions WHERE id=?",
@@ -248,46 +263,57 @@ def cmd_complete(args):
if not os.path.exists(full_scratch):
err({'error': f'Scratch file not found: {args.scratch}',
'hint': 'Worker must write the file before calling complete.'})
# Parse worker notes from the manifest header
worker_notes = parse_notes_from_scratch(full_scratch)
notes_json = json.dumps(worker_notes)
old = row['status']
cur.execute("""
UPDATE functions
SET status='complete', scratch_file=?, translation_notes=?,
SET status='translated', scratch_file=?, translation_notes=?,
assigned_agent=?, updated_at=?
WHERE id=?
""", (args.scratch, notes_json, args.agent, now_utc(), args.id))
log_transition(cur, args.id, old, 'complete', args.agent,
log_transition(cur, args.id, old, 'translated', args.agent,
f'scratch: {args.scratch}')
con.commit()
out({'id': args.id, 'name': row['name'], 'old_status': old,
'new_status': 'complete', 'scratch_file': args.scratch,
'new_status': 'translated', 'scratch_file': args.scratch,
'parsed_notes': worker_notes,
'next_step': 'Integration Agent: run `place --id` after moving to dos_port/src/'})
con.close()
def cmd_place(args):
"""Integration Agent calls this after moving scratch → dos_port/src/."""
"""Integration Agent records placement. Guards against scratch == output."""
con = db()
cur = con.cursor()
row = cur.execute("SELECT id, status, name, scratch_file FROM functions WHERE id=?",
(args.id,)).fetchone()
if not row:
err({'error': f'No function with id={args.id}'})
if row['status'] != 'complete':
err({'error': f'Function {row["name"]} is not complete (status={row["status"]})',
'hint': 'Only complete functions can be placed.'})
if row['status'] != 'translated':
err({'error': f'Function {row["name"]} is not translated (status={row["status"]})',
'hint': 'Only translated functions can be placed.'})
full_output = os.path.join(REPO_ROOT, args.output)
if row['scratch_file']:
full_scratch = os.path.join(REPO_ROOT, row['scratch_file'])
try:
if os.path.realpath(full_scratch) == os.path.realpath(full_output):
err({'error': 'scratch and output resolve to the same file — '
'create a separate scratch file in dos_port/scratch/',
'scratch': row['scratch_file'],
'output': args.output})
except OSError:
pass
cur.execute("""
UPDATE functions
SET output_file=?, scratch_file=NULL, assigned_agent=?, updated_at=?
WHERE id=?
""", (args.output, args.agent, now_utc(), args.id))
log_transition(cur, args.id, 'complete', 'complete', args.agent,
log_transition(cur, args.id, 'translated', 'translated', args.agent,
f'placed → {args.output}')
con.commit()
# Delete the scratch file now that it has been placed in dos_port/src/.
deleted_scratch = None
if row['scratch_file']:
full_scratch = os.path.join(REPO_ROOT, row['scratch_file'])
@@ -295,26 +321,89 @@ def cmd_place(args):
os.remove(full_scratch)
deleted_scratch = row['scratch_file']
except OSError:
pass # already gone — not an error
pass
out({'id': args.id, 'name': row['name'],
'scratch_deleted': deleted_scratch,
'output_file': args.output})
con.close()
def cmd_reset(args):
"""Return any function to needs_translation, preserving category."""
con = db()
cur = con.cursor()
row = cur.execute("SELECT id, status, name, assigned_agent FROM functions WHERE id=?",
(args.id,)).fetchone()
if not row:
err({'error': f'No function with id={args.id}'})
old = row['status']
cur.execute("""
UPDATE functions
SET status='needs_translation', assigned_agent=NULL,
scratch_file=NULL, updated_at=?
WHERE id=?
""", (now_utc(), args.id))
log_transition(cur, args.id, old, 'needs_translation',
row['assigned_agent'], 'reset by maintenance')
con.commit()
out({'id': args.id, 'name': row['name'],
'old_status': old, 'new_status': 'needs_translation'})
con.close()
def cmd_wire(args):
"""Claude Code session: translated → wired."""
con = db()
cur = con.cursor()
row = cur.execute("SELECT id, status, name FROM functions WHERE id=?",
(args.id,)).fetchone()
if not row:
err({'error': f'No function with id={args.id}'})
if row['status'] != 'translated':
err({'error': f'{row["name"]} is not translated (status={row["status"]})',
'hint': 'Only translated functions can be wired.'})
cur.execute("""
UPDATE functions SET status='wired', assigned_agent=?, updated_at=?
WHERE id=?
""", (args.agent, now_utc(), args.id))
log_transition(cur, args.id, 'translated', 'wired', args.agent)
con.commit()
out({'id': args.id, 'name': row['name'],
'old_status': 'translated', 'new_status': 'wired'})
con.close()
def cmd_verify(args):
"""Claude Code session: wired → verified (confirmed working in DOSBox-X)."""
con = db()
cur = con.cursor()
row = cur.execute("SELECT id, status, name FROM functions WHERE id=?",
(args.id,)).fetchone()
if not row:
err({'error': f'No function with id={args.id}'})
if row['status'] != 'wired':
err({'error': f'{row["name"]} is not wired (status={row["status"]})',
'hint': 'Only wired functions can be verified.'})
cur.execute("""
UPDATE functions SET status='verified', assigned_agent=?, updated_at=?
WHERE id=?
""", (args.agent, now_utc(), args.id))
log_transition(cur, args.id, 'wired', 'verified', args.agent)
con.commit()
out({'id': args.id, 'name': row['name'],
'old_status': 'wired', 'new_status': 'verified'})
con.close()
def cmd_pending_placement(_args):
con = db()
rows = con.execute("""
SELECT id, name, source_file, category, scratch_file, assigned_agent
FROM functions
WHERE status = 'complete' AND output_file IS NULL
WHERE status = 'translated' AND output_file IS NULL
ORDER BY source_file, name
""").fetchall()
out({'count': len(rows), 'pending': [dict(r) for r in rows]})
con.close()
def cmd_translation_log_entry(args):
"""Emit a formatted docs/translation_log.md entry ready to append."""
con = db()
row = con.execute("SELECT * FROM functions WHERE id=?", (args.id,)).fetchone()
if not row:
@@ -328,12 +417,12 @@ def cmd_translation_log_entry(args):
except (json.JSONDecodeError, TypeError):
notes = {}
source = fn.get('source_file', '(unknown)')
output = fn.get('output_file') or fn.get('scratch_file') or '(not yet placed)'
registers = notes.get('registers') or '(not recorded)'
hflag = notes.get('hflag') or '(not recorded)'
bug_tags = notes.get('bug_tags') or 'none'
freenotes = notes.get('notes') or '(none)'
source = fn.get('source_file', '(unknown)')
output = fn.get('output_file') or fn.get('scratch_file') or '(not yet placed)'
registers = notes.get('registers') or '(not recorded)'
hflag = notes.get('hflag') or '(not recorded)'
bug_tags = notes.get('bug_tags') or 'none'
freenotes = notes.get('notes') or '(none)'
entry = f"""
## {fn['name']}
@@ -353,7 +442,6 @@ def cmd_translation_log_entry(args):
con.close()
def cmd_recategorize(args):
"""Move a job to a different category and reset it to needs_translation."""
con = db()
cur = con.cursor()
row = cur.execute(
@@ -364,7 +452,7 @@ def cmd_recategorize(args):
err({'error': f'No function with id={args.id}'})
if row['category'] == args.category:
err({'error': f'{row["name"]} is already category={args.category}'})
old_cat = row['category']
old_cat = row['category']
old_status = row['status']
note = args.notes or f'recategorized {old_cat}→{args.category}'
cur.execute("""
@@ -441,7 +529,7 @@ def cmd_log(args):
out({'function': fn_dict, 'log': [dict(r) for r in log]})
con.close()
# ── CLI wiring ──────────────────────────────────────────────────────────────────
# ── CLI wiring ──────────────────────────────────────────────────────────────────
def main():
ap = argparse.ArgumentParser(description=__doc__,
@@ -452,8 +540,7 @@ def main():
p = sub.add_parser('list', help='List functions')
p.add_argument('--category', choices=['simple', 'complex'])
p.add_argument('--status',
choices=['needs_translation','in_progress','unverified','complete','skip'])
p.add_argument('--status', choices=ALL_STATUSES)
p.add_argument('--limit', type=int)
p = sub.add_parser('claim', help='Claim jobs for an agent')
@@ -465,10 +552,10 @@ def main():
p.add_argument('--id', type=int, required=True)
p = sub.add_parser('complete',
help='Worker marks job done; parses notes from scratch file header')
help='Worker marks job translated; parses notes from scratch file')
p.add_argument('--id', type=int, required=True)
p.add_argument('--scratch', required=True,
help='Repo-relative path to the scratch file (dos_port/scratch/…)')
help='Repo-relative path to scratch file in dos_port/scratch/')
p.add_argument('--agent')
p = sub.add_parser('place',
@@ -478,6 +565,20 @@ def main():
help='Repo-relative path of the placed file in dos_port/src/')
p.add_argument('--agent')
p = sub.add_parser('reset',
help='Return any function to needs_translation (preserves category)')
p.add_argument('--id', type=int, required=True)
p = sub.add_parser('wire',
help='Claude Code: mark function as wired into live game loop')
p.add_argument('--id', type=int, required=True)
p.add_argument('--agent')
p = sub.add_parser('verify',
help='Claude Code: mark function as verified working in DOSBox-X')
p.add_argument('--id', type=int, required=True)
p.add_argument('--agent')
p = sub.add_parser('recategorize',
help='Move a job to a different category (resets to needs_translation)')
p.add_argument('--id', type=int, required=True)
@@ -486,13 +587,13 @@ def main():
p.add_argument('--notes')
sub.add_parser('pending-placement',
help='List complete jobs waiting for Integration Agent')
help='List translated jobs waiting for Integration Agent')
p = sub.add_parser('translation-log-entry',
help='Emit a formatted translation_log.md entry for Docs_Commit_Agent')
p.add_argument('--id', type=int, required=True)
p = sub.add_parser('unclaim', help='Return a job to the queue')
p = sub.add_parser('unclaim', help='Return a claimed job to needs_translation')
p.add_argument('--id', type=int, required=True)
p = sub.add_parser('fail', help='Return a job with a failure note')
@@ -504,18 +605,21 @@ def main():
args = ap.parse_args()
{
'status': cmd_status,
'list': cmd_list,
'claim': cmd_claim,
'manifest': cmd_manifest,
'complete': cmd_complete,
'place': cmd_place,
'pending-placement': cmd_pending_placement,
'translation-log-entry': cmd_translation_log_entry,
'recategorize': cmd_recategorize,
'unclaim': cmd_unclaim,
'fail': cmd_fail,
'log': cmd_log,
'status': cmd_status,
'list': cmd_list,
'claim': cmd_claim,
'manifest': cmd_manifest,
'complete': cmd_complete,
'place': cmd_place,
'reset': cmd_reset,
'wire': cmd_wire,
'verify': cmd_verify,
'pending-placement': cmd_pending_placement,
'translation-log-entry': cmd_translation_log_entry,
'recategorize': cmd_recategorize,
'unclaim': cmd_unclaim,
'fail': cmd_fail,
'log': cmd_log,
}[args.cmd](args)
if __name__ == '__main__':