PR Babysitter
openinterpreter/openinterpreter
Watches an open GitHub pull request until it merges, handling review comments, diagnosing CI failures and retrying flaky checks along the way.
Build os8088 and publish the floppy images to the os8088.com website repo as a pull request, plus a GitHub release on the OS repo.
$ npx skills add jggonz/os8088 --skill release-os8088 -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install jggonz/os8088 release-os8088 --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/jggonz/os8088.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/release-os8088 .claude/skills/release-os8088 && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "release-os8088" agent skill from https://github.com/jggonz/os8088/tree/main/.claude/skills/release-os8088 into .claude/skills/release-os8088/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "release-os8088", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/jggonz/os8088/tree/main/.claude/skills/release-os8088Type this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add jggonz/os8088 --skill release-os8088 -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install jggonz/os8088 release-os8088 --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/jggonz/os8088.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.claude/skills/release-os8088 .agents/skills/release-os8088 && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "release-os8088" agent skill from https://github.com/jggonz/os8088/tree/main/.claude/skills/release-os8088 into .agents/skills/release-os8088/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "release-os8088", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add jggonz/os8088 --skill release-os8088 -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install jggonz/os8088 release-os8088 --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/jggonz/os8088.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.claude/skills/release-os8088 .cursor/skills/release-os8088 && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "release-os8088" agent skill from https://github.com/jggonz/os8088/tree/main/.claude/skills/release-os8088 into .cursor/skills/release-os8088/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "release-os8088", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/jggonz/os8088.git --path .claude/skills/release-os8088--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add jggonz/os8088 --skill release-os8088 -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install jggonz/os8088 release-os8088 --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/jggonz/os8088.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.claude/skills/release-os8088 .gemini/skills/release-os8088 && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "release-os8088" agent skill from https://github.com/jggonz/os8088/tree/main/.claude/skills/release-os8088 into .gemini/skills/release-os8088/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "release-os8088", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install jggonz/os8088 release-os8088Installs for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add jggonz/os8088 --skill release-os8088 -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/jggonz/os8088.git skills-src && mkdir -p .github/skills && cp -r skills-src/.claude/skills/release-os8088 .github/skills/release-os8088 && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "release-os8088" agent skill from https://github.com/jggonz/os8088/tree/main/.claude/skills/release-os8088 into .github/skills/release-os8088/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "release-os8088", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add jggonz/os8088 --skill release-os8088 -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install jggonz/os8088 release-os8088 --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/jggonz/os8088.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.claude/skills/release-os8088 .opencode/skills/release-os8088 && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "release-os8088" agent skill from https://github.com/jggonz/os8088/tree/main/.claude/skills/release-os8088 into .opencode/skills/release-os8088/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "release-os8088", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
release-os8088Build os8088 and publish the floppy images to the os8088.com website repo as a pull request, plus a GitHub release on the OS repo.
Release Os8088 is an agent skill from jggonz/os8088. Build os8088 and publish the floppy images to the os8088.com website repo as a pull request, plus a GitHub release on the OS repo. Use when the user asks to cut a release, publish a new build, ship the latest images to the website, or update os8088.com with a new version.
Its SKILL.md is about 10k tokens, which your agent loads only when the skill is triggered. The skill folder holds 2 other files (for example `emusmoke.py` and `mkzip.py`).
It sits in Development, covering Pull requests. It works with GitHub. The licence is MIT.
7 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 95f7e97. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Ships script files (Python), which the agent can run.
Shell commands in SKILL.md call:
makegitpython3ghmagickcurlFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
youtube.comclaude.comFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Release Os8088 loads about 10k tokens when it runs. Until then it costs about 72 tokens; SKILL.md has 5,630 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from jggonz/os8088 at commit 95f7e97, republished under its MIT licence (© jggonz). 5,630 words, ~10,406 tokens.
.claude/skills/release-os8088/SKILL.md (or your agent's skills folder). This skill also uses 2 other files; get the full folder from GitHub.Builds the floppy images, publishes them into the website repository next door along with the notes for its releases page, opens a pull request there, and cuts a GitHub release on this repo.
The GitHub release carries ONE asset: a zip. os8088-<version>.zip holds
every image, a README.md that explains every file in it -- grouped by what the
reader is trying to do, so the pair most people need comes first and each group
says how to use its own files -- and a SHA256SUMS covering all of them. Eleven
loose .img files used to hang off the release, and a reader had to know which
pair they wanted before they could download anything. Step 3a builds it. The website is unchanged -- its download
page still serves the images individually out of public/disk/, because the
browser demo streams them and a table of per-image checksums is the point of
that page.
The release notes get written once and used twice: data/releases.json in the
website repo (step 4a) and the GitHub release (step 6) say the same thing, and
the site's /releases/ page is that file rendered.
When the release adds a whole program -- or there is a video about it -- it
also gets a Spotlight page (step 4b), which is a page of its own under
/spotlight/ with its own screenshots. A line on the releases page does not
carry a new program.
Read "Writing the copy" below before writing a word of any of them.
The reader is someone who found the project and is curious. They may write assembly, or they may just like old computers. Write so both finish the sentence. Assume interest, never knowledge.
Say it in this order: what changed, what it does now, and what that means for someone using it. Nothing else is required.
Rules, in order of how often they get broken:
§ number, a source label, an .inc file, a
register name or a symbol like gfx_fill means nothing outside this repo.
If a spec section is the authority, name it in a short closing sentence --
never use it as the explanation.The check before you commit: read each sentence and ask whether someone who has never opened this repository understands it. If they would have to, rewrite it or cut it.
An example of the difference, on a real change:
Too dense: The player now gates widget drawing on a word of dirty bits, applying the period's update-region idea at the widget -- 28,365 glyph cells down to 2,468 over the same ten seconds of playback, per the spec section that owns it.
Write this instead: The player used to redraw its whole face every time the screen updated, which was more work than the machine could finish between frames, so playback stuttered. It now redraws only the parts that changed. Over the same ten seconds of music that is 2,468 character cells drawn instead of 28,365.
Lengths that fit the page and stay readable:
| field | length |
|---|---|
summary | 2-4 sentences. What this release is, leading with the one thing that matters most. |
highlights[].body | 2-5 sentences. One change each. |
notes | 1-3 sentences, written as instructions to the reader. |
Never hardcode an absolute path. Resolve both from the current checkout, and use these variables in every command below:
OS_REPO="$(git rev-parse --show-toplevel)"
WEB_REPO="${WEB_REPO:-$OS_REPO/../os8088-web}"| repo | what it is | role |
|---|---|---|
$OS_REPO | this checkout | builds the images, gets the git tag and GitHub release |
$WEB_REPO | a sibling checkout of the website | receives the images, the manifest and the release notes, gets the pull request |
If $WEB_REPO does not exist, the website half is simply not available in
this working copy. Say so plainly, do the build and the GitHub release
anyway (steps 1-3 and 6), and skip steps 4 and 5. Do not go looking around
the filesystem for it, and do not clone it uninvited -- offer, and let the
user decide. If the user keeps their website checkout somewhere else, they can
point at it with WEB_REPO=/path/to/os8088-web.
$1 (optional) -- the release version, e.g. v1.0.20260727. If the user did
not give one, derive it: take the version the OS reports in its own About box
(grep -n 'os8088' kernel/apps.inc -- currently 1.0) and append today's
date, giving v<osversion>.<YYYYMMDD>. Tell the user the version you picked.--shots -- also recapture every screenshot from the new build. Do this
whenever the change touches anything visible. It boots ~15 QEMU instances and
takes a few minutes.--no-pr -- do everything except opening the pull request and the release.Run these and stop if any fails, reporting exactly what is wrong:
cd "$OS_REPO"
git status --porcelain # uncommitted OS changes?
git rev-parse --abbrev-ref HEAD
command -v nasm qemu-system-i386 python3
gh auth status
ls "$WEB_REPO/tools/release.py"If the OS working tree is dirty, stop and ask whether to release anyway -- the manifest records the commit hash, and releasing uncommitted work makes that hash a lie. If the user says go ahead, note it in the PR body.
If the website repo has uncommitted changes, stop and ask; the release commits
to a fresh branch off main and would otherwise sweep unrelated work into it.
cd "$OS_REPO"
make clean && make && make emu
ls -l build/os8088.img build/os8088-120.img build/os8088-720.img \
build/os8088-360.img \
build/apps.img build/apps120.img build/apps720.img build/apps360.img \
build/media360.img \
build/office360.img build/network360.img build/games360.img \
build/emu.imgAll thirteen must exist -- step 3a refuses to pack without them. (Four geometries of each pair since SPEC.md 19's 1.2MB disk, plus the 360KB-only media disk and SPEC.md 24.6's three 360KB-only category disks -- at that size the spreadsheet and the chart viewer ship on the office disk and on NO other.)
The build enforces its own invariants -- a 512-byte boot sector and a kernel that fits under offset 0xA000 -- so a build failure here is a real problem, not something to work around. Report the kernel size; if it has grown, say by how much and how much headroom is left (the ceiling is 0xA000 = 40,960 bytes for image + bss).
make emu is not optional, though make does not run it. It builds
build/emu.img, the emulator system disk (SPEC.md 9.11.7): the kern_emu
kernel -- kern_big plus the VMware absolute pointer, VMMOUSE.DRV -- with a
SYSTEM.CFG that already switches the driver on. In QEMU, VMware, VirtualBox
and the v86 browser emulator the pointer then follows the host mouse with no
grab. It needs nothing a bare make does not, which is why mkzip.py treats
it as required rather than on-demand. Three things about it that are decided
and not to be "fixed" at release time:
apps.img. kern_emu holds the same API table
at the same offsets, so there is no emu software disk and must not be.--size 720 if it
is ever wanted; that is a Makefile change, not a release step.build/emuk/, never in build/, so nothing in the
rest of this procedure boots it by accident.Then the on-demand disks, which make does not build and the zip carries
when they exist:
tools/setup-cc.sh # SmallerC; needed by cword and allapps
make allapps # apps-all-N.img -- every program, a set of disks
make worddisk cworddisk # word*.img, cword*.img
make c64disk # c64*.img
make weavedisk # weave*.img -- the Weave family's disk
make loomdisk # loom*.img -- the same family's IDE disk,
# with the demo SOURCES instead of the
# compiled bundles
make runcpm-src && make runcpmdisk # runcpm*.img
make scribedisk paccmandisk # scribe*.img, paccman*.img
make 1942disk redlinedisk # 1942*.img, redline*.img
make apple2rom && make apple2disk # apple2*.img -- apple2rom fetches the
# ROM once; `make clean` spares it
make live # os8088-usb.img + os8088.iso -- the live
# USB image and the live CD (SPEC.md 80).
# Needs the fetch on the line above and
# the C toolchain, like allapps
make usb-emu # os8088-emu-usb.img -- THE WEBSITE'S DEMO
# DISK (SPEC.md 80.7), the same volume on
# kern_emu with VMMOUSE.DRV wanted. Not
# in the zip; the web repo's release.py
# publishes it, and skipping it leaves
# the demo on last release's buildOffer these, do not assume them. If tools/setup-cc.sh cannot run -- no
network, no host toolchain -- release the thirteen and say which on-demand disks
were skipped; they are a convenience, and a release that waits on one is a
release that does not happen. mkzip.py prints the ones it did not find, so
that list is generated rather than remembered. Boot any that were built in step
3 like any other image (they go in B:, make test TESTAPPS=build/word.img).
The live media carries the story files, and that is decided. Since #188,
make live puts every story in tools/getstories.py's MANIFEST on the live
USB image and the live CD, so the zip carries them inside those two files. The
user decided at v1.0.20260916 that this ships: the MANIFEST is curated to files
that are free to redistribute -- three Infocom and Activision giveaways (the
two Samplers, Mini-Zork, ZTUU) and authors' own freeware -- and none of the
Infocom games that were sold. Do not stop to ask about it again.
What still stays out is the story disk as a zip entry. zork*.img is not in
mkzip.py's ITEMS, and STORIES= can add files a user owns but may not
redistribute, so never run a release build with STORIES= set. If a new
entry is proposed for the MANIFEST, it has to be free to redistribute, because
it ships in the next release.
Never publish an image that has not been booted.
cd "$OS_REPO"
rm -f build/qmp.sock build/qemu.pid
make test
sleep 8
python3 tools/qmp.py build/qmp.sock 'screendump build/smoke.ppm'
python3 tools/qmp.py build/qmp.sock 'quit'
magick build/smoke.ppm build/smoke.png # or: convert, on older ImageMagickLook at build/smoke.png with the Read tool. You are checking for: the
menu bar across the top with the chip glyph, then Locator, File and Builtins;
a Disk A and a Disk B icon down the right-hand side; the mouse pointer.
If the screen is blank or garbled, stop -- do not publish. Delete the two
scratch files afterwards; build/ is gitignored, but leave it tidy.
Then boot the emulator disk -- emu.img with apps.img, the README's own
QEMU line with no mouse on it:
python3 .claude/skills/release-os8088/emusmoke.py --shot build/smoke-emu.pngIt must print PASS. A screenshot alone cannot pass this disk: an emulator disk
whose driver never attached boots to the same desktop, silently on the serial
mouse, which is the one failure that matters here. So the script reads the
answer out of the guest -- the driver attached, the backdoor won the mouse, and
three absolute positions sent through QMP land where they were sent -- and kills
only the QEMU it started. Then look at build/smoke-emu.png the same way as
smoke.png: the same desktop, with the pointer in the middle of the screen,
where the script left it. Delete it afterwards.
If make live ran, boot both live images the same way -- the rule is
per image, and a zip does not exempt the two biggest files in it:
qemu-system-i386 -display none -qmp unix:build/qmp.sock,server,nowait \
-drive file=build/os8088-usb.img,format=raw -boot c \
-chardev msmouse,id=m0 -serial chardev:m0 &
sleep 10
python3 tools/qmp.py build/qmp.sock 'screendump build/smoke-usb.ppm'
python3 tools/qmp.py build/qmp.sock 'quit'
# ...then the same five lines with `-cdrom build/os8088.iso -boot d`Look at both screenshots. The desktop must show a Disk A icon AND a Disk C icon down the right-hand side -- C: is the live partition the kernel adopted, and its absence means the image booted the kernel and lost the volume, which is exactly the failure a screenshot catches and a checksum cannot.
The one asset the GitHub release gets. Build it only after step 3 has booted the images -- the zip is what people download, and packing an unbooted image is the same mistake with an extra layer of wrapping on it.
cd "$OS_REPO"
python3 .claude/skills/release-os8088/mkzip.py --version "$VERSION"It writes build/os8088-<version>.zip and prints the entry count, the packed
and unpacked sizes, the zip's own sha256, and which on-demand disks were not
built. Quote that sha256 in the release notes -- it is the only checksum a
reader of the GitHub release gets, since there is no longer a per-file table
there.
The script does three things worth knowing about:
build/ also holds the story
disks and every test gate's scratch image, and a glob ships those the first
time somebody runs an unrelated target before cutting a release. If a new
disk should be in releases, add it to ITEMS in mkzip.py, in the group a
reader would look for it under -- that is the only place the list lives, and
the README's description of it is written there too. A disk that fits no
group gets a new entry in GROUPS, with the instructions for using it.make or
make emu builds stops it; a missing on-demand disk is reported and skipped.Then look inside it, the same way step 3 makes you look at the screenshot:
cd "$(mktemp -d)" && unzip -q "$OS_REPO/build/os8088-$VERSION.zip"
cd "os8088-$VERSION" && shasum -a 256 -c SHA256SUMS && cat README.mdRead the README with the Read tool. It is written for someone who has never
seen this project, so "Writing the copy" governs it exactly as it governs the
release notes. It is README.md: Markdown that reads the same in Notepad as
on GitHub, generated from two tables in mkzip.py:
GROUPS -- the situations a reader is in, in the order the README takes
them: Start here (the system and software pair per disk size, as a table),
Extra disks for a 360KB machine, In an emulator or virtual machine (the
emulator disk), No floppy drive (the live USB image and CD), Every program,
on a set of disks, One program per disk, and About this zip (checksums
and licence files). Each carries the instructions for using its own files --
the QEMU line, the dd line -- so they sit beside the files they are about.ITEMS -- the things a reader chooses between, each in one group, with
the images it comes as and a line saying what it is for. This is also the
allowlist of what can go in the zip.Only what was packed is described, and a group with nothing in this zip is left out whole. Check that every file in the unpacked directory has its own line in the README, that the version and commit are this release's, and that no group explains a file that is not there.
The QEMU command lines in it are the real ones -- if make run's invocation
ever changes, GROUPS in mkzip.py has to change with it, and the way to know
is to run the commands out of the unpacked directory and see the desktop come
up. The emulator group's line is the one emusmoke.py boots, and it has no
mouse line on purpose: QEMU answers the absolute pointer by default, and the
serial mouse beside it would be a second pointing device with the buttons split
across the two.
cd "$WEB_REPO"
git checkout main && git pull --ff-only
git checkout -b "release/$VERSION"
python3 tools/release.py --version "$VERSION" --os-repo "$OS_REPO" [--shots]Pass --os-repo explicitly rather than relying on its default, which assumes
the OS checkout is the sibling directory ../jop.
release.py copies the four images into public/disk/, regenerates the
gzipped copies the browser demo streams, writes public/releases.json, adds
this release to data/releases.json, and rebuilds the site. The download
page's table of sizes and checksums is generated from that manifest at build
time, so it cannot drift.
data/releases.jsonThis is yours to write -- the script cannot. release.py fills in only
what it reads off the build: version, date, commit, kernel size, file list. It
leaves summary, highlights and notes empty and prints a reminder saying
so. The releases page ($WEB_REPO/site/releases.html, published at
os8088.com/releases/) renders them, so an unfilled entry ships as a version
number with no story attached.
Write the same words you are about to put in the GitHub release notes in step 6 -- write them once, here, and reuse them there. Follow "Writing the copy" above for all three fields:
summary -- what this release is, in 2-4 plain sentences, most important
thing first.highlights -- one entry per change worth reading about: title, the
optional PR number as issue, and a body that explains it rather than
restating the title. Titles are plain too: name the change, do not sell it.
HTML is allowed in body; keep it ASCII, and use -- the way the rest of
the site does.notes -- anything that changes how the system is used and would
otherwise surprise someone (a menu item that moved, a default that flipped).
Write it as an instruction to the reader. Rendered as a call-out.Check whether the images actually changed -- git diff --stat HEAD -- public/disk/ in the website repo, after release.py has run. The build is
deterministic, so a release whose work was all in a package or on the story
disk produces four images byte for byte identical to the previous release.
That is a fine release to cut, and it is a lie by omission not to say so: the
headline feature is not in the download, and someone will boot the image
looking for it. Put it in the summary and again in notes.
The optional ramBytes / ramCap / sourceLines / modules fields render
the size figures on the page. ramCap is KERN_CODE_MAX -- 65536, and
read it out of kernel/kernel.asm rather than trusting this line, because it
has moved once already. The other three are not printed by the build, so
measure them -- from $OS_REPO:
# ramBytes: image + .bss, the number the build-time assertion guards.
# The kernel refuses to assemble over the cap, so bypass the %error to read it.
sed 's/%error "kernel too big.*/%warning bypassed/' kernel/kernel.asm > /tmp/ksz.asm
printf '%%assign KT KTEXT_SIZE\n%%assign KB KBSS_SIZE\n%%warning KTEXT=KT KBSS=KB\n' >> /tmp/ksz.asm
nasm -f bin -I kernel/ -o /dev/null /tmp/ksz.asm # warning prints both; ramBytes = KT + KB
# sourceLines and modules: the boot sector, kernel.asm and everything it
# actually includes, the SDK header, and every package's .asm AND the .inc
# files that .asm includes. The dead kernel .inc files are not included by
# anything and do not count; neither does apps/frotz/zharness.inc, which is
# development-only and never in a shipped build.
#
# ANCHOR THE GREP. `grep '%include'` also matches the word in a comment, and
# both figures were wrong for it: a stray match became a garbage filename wc
# silently skipped, and `grep -c` counted it as a 35th kernel module when
# there are 34.
python3 - <<'EOF'
import glob, os, re
def n(p):
return open(p, 'rb').read().count(b'\n')
def incs(path, dirs):
out = []
for f in re.findall(r'^\s*%include\s+"([^"]+)"', open(path).read(), re.M):
for d in dirs: # an .asm names its include EITHER
q = os.path.normpath(os.path.join(d, f)) # beside itself or
if os.path.exists(q): # apps/-relative, because that is the
out.append(q) # -I nasm is given. Resolve BOTH: the
break # apps/-relative form is how cword and
return out # runcpm name theirs, and only trying
# the sibling form silently drops them
kern = incs('kernel/kernel.asm', ['kernel'])
apps = sorted(glob.glob('apps/*/*.asm'))
pkg = []
for a in apps:
for q in incs(a, [os.path.dirname(a), 'apps']):
if q not in pkg and 'zharness' not in q and 'crt0' not in q:
pkg.append(q) # crt0.asm is the C runtime, counted
# once with the SDK rather than per
# C package that includes it
files = ['boot/boot.asm', 'kernel/kernel.asm'] + kern + ['apps/os88api.inc'] + apps + pkg
files = list(dict.fromkeys(files))
print('sourceLines', sum(n(p) for p in files if os.path.exists(p)))
print('modules ', len(kern))
# The C is NOT in sourceLines -- the page labels that figure "lines of
# assembly". Count it separately and quote it in the entry when it moves.
csrc = [p for p in sorted(glob.glob('apps/*/*.c') + glob.glob('apps/*/*.h'))
if 'hosttest' not in p]
print('C lines ', sum(n(p) for p in csrc))
EOFThis recipe changed AGAIN at v1.0.20260818 and the series steps there too.
Package includes are named apps/-relative as often as they are named beside
the .asm -- %include "cword/cwmove.inc" -- and resolving only the sibling
form dropped them, cword's and runcpm's bulk among them; the same pass also
counted apps/os88api.inc and apps/cc/os88thunk.asm twice, once as a glob
hit and once as an include, hence the dict.fromkeys. Net 9,324 lines out on
that tree: the old recipe reported 222,280 lines, this one 231,604, and the
previous release recounts to 228,855 -- which is the number that entry's
note quotes, so the +2,749 that is real growth is not read as +9,765.
Entries before v1.0.20260818 still hold their own recipe's numbers.
And it changed at v1.0.20260810 before that. It used
to glob apps/*/*.asm only, which missed the .inc files four packages keep
their bulk in -- ArtfulType, ModPlug, Tracker and Frotz, 37,309 lines between
them at that release. The old recipe reported 115,528 lines and 35 modules for
that tree; this one reports 152,837 and 34, and data/releases.json was
restated to the new figures rather than left carrying a known undercount.
Entries before it still hold old-recipe numbers and are not being recounted --
so the step is between v1.0.20260809 and v1.0.20260810, and the entry says
so. When a figure jumps because the counting changed, say so where the
figure is, or the jump reads as growth that did not happen.
If you update these, the same figures are hardcoded in the website's prose --
site/index.html, site/faq.html, site/how-it-works.html,
site/download.html and site/how-it-works/graphics.html all quote the kernel
size, the RAM footprint, the headroom or the line count. Grep the old numbers
across $WEB_REPO/site/ and fix them in the same PR; nothing validates them.
Then rebuild and verify:
python3 tools/build.py # must report 0 problems
python3 tools/linkcheck.py # must report 0 dead
git status --porcelainLook at the releases page before you commit it. Serve public/ and read
it, the same way step 3 makes you look at the smoke screenshot -- a release
whose entry renders as an empty window is worse than no page at all:
(cd "$WEB_REPO/public" && python3 -m http.server 8099 &) && sleep 2
# then open http://localhost:8099/releases/ and check this release's window
# has its summary, its highlights, its figures and its four files/spotlight/ is the hub for one-page write-ups: a new program, a feature too
big for a highlight, or anything with a video about it. site/spotlight.html
is the index and site/spotlight/<name>.html is the page. The nav already
carries Spotlight (File menu in tools/build.py, and the footer dock in
site/_layout.html), so a new page needs no wiring beyond its own entry on the
index.
Decide first, and it is usually no. A bug fix, a speed-up or a new menu item is a highlight on the releases page and nothing more. Write a Spotlight page when a reader would want a page: a program that did not exist before, or a video that needs somewhere to live. If in doubt, ask the user rather than producing a page nobody asked for.
1. Capture the screenshots. They come out of the emulator, never a mockup. Scenes live in the website repo beside the others:
cd "$WEB_REPO"
# scenes.frotz.json is the worked example: five scenes, one per story.
python3 tools/capture.py --scenes tools/scenes.<name>.json \
--out public/img/<name> --repo "$OS_REPO" --jobs 3A scene may set "diskB": "<image>.img" to put a different floppy in drive B:
than the software disk -- that is how Frotz's scenes reach the story disk
make zdisk builds. Whatever the page shows must be built first; make alone
does not build an on-demand disk.
Look at every captured PNG with the Read tool. A lost click gives a plausible-looking screenshot of the wrong thing, and file size will not tell you: two of Frotz's five first came out sitting on an unanswered "Do you need instructions?" prompt with an otherwise empty window.
2. Write the page. Copy site/spotlight/frotz.html and change it. Set
body_class: spot in the metadata block -- that is what turns on the article
layout, and without it the page is the ordinary stack of one-screendump-wide
windows, which is what these pages exist to stop being.
The layout has two widths and nothing between: 800px for anything with
sentences in it, the full two columns for anything to look at. Alternating them
is the structure. The pieces, all in the stylesheet's spotlight section:
| masthead | a .grid of two: the pitch, .specs with three numbers, the buttons and a .spot-toc of jump links -- beside the one screendump that proves it |
.spot-band | inverted full-width section heading, each with one line saying why you would read that section. These are the anchors somebody skims by |
| gallery | the screendumps in a plain .grid, two abreast |
.spot-cards | a .grid of short titled cards, for what would otherwise be one window with four h3s in it |
.spot-steps | numbered instructions, for the part somebody follows while typing |
Order: masthead, what it is for someone who has never heard of it, the video, the gallery, how it works, what it deliberately does not do, how to run it.
The copy rules above apply, plus one more: a Spotlight page is written for
someone who does not know the subject at all, so explain the domain and not
only the change. The Frotz page spends three paragraphs on what a Z-machine is
before it says a word about the implementation. That is the right proportion.
Still no marketing, still numbers instead of adjectives, and still no §
numbers or symbol names.
Two things a reader gets in the first five seconds, so write them last and
hardest: the deck (.spot-deck, one or two sentences that are the whole
page) and the three numbers in the .specs block. If you cannot fill those
three cells with facts, the page is probably a highlight and not a spotlight.
The copy rules above apply, plus one more: a Spotlight page is written for
someone who does not know the subject at all, so explain the domain and not
only the change. The Frotz page spends three paragraphs on what a Z-machine is
before it says a word about the implementation. That is the right proportion.
Still no marketing, still numbers instead of adjectives, and still no §
numbers or symbol names.
3. Embed the video, if there is one. Reuse the markup on the Frotz page --
an <a class="vid__poster" data-video="<id>"> around an
<img src="/videos/thumb/<id>.jpg">, plus <script src="/js/videos.js" defer>
at the foot of the page. That gets the same bargain /videos/ makes: the
poster is a plain link until someone presses play, the still is proxied through
this site so loading the page tells YouTube nothing, and the embed is
youtube-nocookie.com, which is the one host frame-src allows.
Get the real title from YouTube rather than inventing one:
curl -sS "https://www.youtube.com/oembed?url=https%3A//www.youtube.com/watch%3Fv%3D<id>&format=json"/videos/thumb/ is served by the Worker, not from public/, so the poster
is a broken image on a local http.server and correct in production. That is
expected. tools/linkcheck.py knows -- WORKER_ROUTES -- and a new
Worker-served path has to be added there or the link check fails on a link that
works.
4. Add it to the index and link it. One figure.win.shot block at the top
of the list in site/spotlight.html (newest first), and a pointer from
wherever a reader would otherwise look for it -- for Frotz that is
site/applications.html, which lists the programs it is not among. Link the
page from the matching highlights[].body in data/releases.json too.
5. Rebuild, link-check, and look at both pages in the browser, the same way step 4a makes you look at the releases page. Three things that will fool you:
build.py does not touch it. public/css/
is committed by hand, so a CSS change plus a reload shows you the old page. A
band that renders as plain text on the dither is that, not a broken rule.The site is now where the machine gets its software from (SPEC.md 92), so
a release ships a catalog as well as four floppy images, and nothing on the
OS side can tell you it went wrong: a stale catalog.bin is a Wire that lists
last release's programs at last release's sizes and hands out last release's
bytes, and the window looks perfectly healthy while it does it.
Three things, in this order:
1. tools/release.py fills public/wire/pkg/. It copies every file
data/wire.json names out of <os-repo>/build/ — the .o88s and their
sidecars — the way it already copies floppy images into public/disk/. A name
it cannot find is a refusal, not a warning. If a package was added, renamed
or split this release, data/wire.json needs the edit before this runs, and
the tier and the description are a judgement somebody makes rather than a
field to fill: check the entry against that program's own SPEC section or its
Spotlight page, and flag every tier you touched as reviewable in the PR.
2. The site build packs the catalog. tools/wire.py, called by
build.py, writes public/wire/catalog.bin and public/wire/pic/<STEM>.PIC
from data/wire.json and public/wire/pkg/. It is a build product committed
under public/ like every other one, so the deploy job's
git status --porcelain public/ check is what catches a build that was not
re-run.
3. Check it changed, and verify it FROM THE OS REPO. A package changed and a catalog that did not is the failure this step exists for:
git -C "$WEB_REPO" status --porcelain public/wire/
python3 tools/os88wire.py --verify "$WEB_REPO/public/wire/catalog.bin" \
--pkgdir "$WEB_REPO/public/wire/pkg"
python3 tools/os88wire.py --dump "$WEB_REPO/public/wire/catalog.bin"Run from the OS repo on purpose. tools/os88wire.py and the website's
tools/wire.py are two independent writers of one format (SPEC.md 92.2), and
this is the one moment they meet: the OS repo's reader checking the website's
bytes, with --pkgdir cross-checking every declared size and every embedded
icon against the files actually published. Read the --dump output against
what you know shipped — a missing program, a stale size or a NEW mark left
on last release's entry are all things only a person notices.
Nothing may redirect http:// to https:// for /wire/*. The machine
cannot follow one, and a redirect there is a Wire that says
The Wire did not answer (301) on every fetch.
cd "$WEB_REPO"
git add -A
git commit -m "Release $VERSION"
git push -u origin "release/$VERSION"
gh pr create --title "Release $VERSION" --body "<body>"The PR body should state: the version, the OS commit it was built from, the
kernel size and its change since the last release, the four image sizes, and
whether screenshots were recaptured. If --shots ran, say which screenshots
actually changed (git diff --stat public/img/shots/) -- if a UI change was
expected and nothing changed, that is a signal something is wrong.
Check the diff includes data/releases.json with its prose filled in. A
release PR that touches the images and the manifest but not the release log
means step 4a was skipped.
If step 4b produced a Spotlight page, say so in the PR body and give its path, list the scenes captured, and name the video it embeds. Mention that the video poster only resolves once the Worker is serving the page, so a reviewer reading a local build does not file it as a broken image.
End the PR body with:
🤖 Generated with [Claude Code](https://claude.com/claude-code)Only after the images are booted and the zip is unpacked and checked.
cd "$OS_REPO"
git tag -a "$VERSION" -m "os8088 $VERSION"
git push origin "$VERSION"
gh release create "$VERSION" \
--title "os8088 $VERSION" \
--notes "<notes>" \
"build/os8088-$VERSION.zip"One asset, and it is the zip. Do not attach loose .img files beside it --
that is the arrangement this replaced, and half of each is worse than either.
Confirm the file exists before running the command: a missing asset makes gh release create fail with the tag already pushed, which is the one failure in
this whole procedure that cannot simply be re-run.
Because the release page no longer lists a file per image, the notes have to
say what the zip contains and how big it is -- one sentence naming the two
images a reader actually needs, the packed size, and the sha256 step 3a
printed. When the zip carries the live media, name them too: a reader with no
floppy drive needs to hear that os8088-usb.img and os8088.iso boot a PC
or an emulator directly, or the two files that serve them best read as
padding. Name the emulator disk the same way: someone running os8088 in QEMU,
VMware, VirtualBox or a browser should hear that emu.img goes in drive A in
place of os8088.img and that the pointer then follows their own mouse with no
click to capture it. Someone who wants a per-image checksum finds it in the SHA256SUMS
inside the zip, or on the download page, and the notes should say which.
gh infers the repository from the checkout's remote, so this works for a
fork or a rename without any edit here.
The notes should name what changed since the previous tag
(git log <prev>..HEAD --oneline), the kernel size, and point at the download
page of whatever site this project publishes to (os8088.com/download/ for the
upstream project). A commit log is not release notes -- "Writing the copy"
applies here exactly as it does on the website, and a reader who lands on the
release from a search has no more context than one who lands on the page.
These are the notes you already wrote in step 4a. Say the same thing in both
places -- the releases page exists to mirror this, and the two disagreeing is
worse than either alone. If the wording improved while writing these, go back
and update data/releases.json to match before the website PR is merged.
Tell the user: the version, the PR URL, the release URL, the kernel size and
its delta, the zip's packed size and how many images went into it, the
Spotlight page's URL if step 4b ran, and anything you skipped or that needs
their attention -- naming any on-demand disk that did not get built, since a
reader of the zip's README cannot tell a disk that was left out from one that
does not exist. If the website PR is merged, the site's own CI
deploys from its main branch -- say so, and say the site is not live until
that merge happens.
Driving the OS to take screenshots is also the most thorough anyone uses it all week, and it turns defects up. Report those separately from the release, with what you did and what happened -- they are not release business, and burying them in a status line is how they get lost.
build/ -- the
ITEMS table in mkzip.py is the list, and it exists so a story disk or a test
gate's scratch image cannot ride out with a release.emusmoke.py
is how step 3 knows, and a desktop screenshot is not.mkzip.py writes the README, the checksums
and a reproducible archive; a zip -r by hand gets none of those and looks
identical until somebody checks.releases.json, which release.py computes from the actual bytes.LICENSE is in the root of this repo and the FAQ
says so. Do not restate the terms in release copy, and do not claim a
different one.main.© jggonz, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 2 other files in .claude/skills/release-os8088 of jggonz/os8088.
Open the folder on GitHubat commit 95f7e97
Release Os8088 next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Release Os8088 this skilljggonz/os8088 | 104 | — | ~10k | Automated safety check: Pass | MIT | |
| PR Babysitteropeninterpreter/openinterpreter | 69k | 3 repos | ~4.2k | Automated safety check: Pass | Apache-2.0 | |
| Check PRonyx-dot-app/onyx | 32k | 2 repos | ~2.3k | Automated safety check: Pass | MIT | |
| Contributor-First PR MergeHKUDS/OpenHarness | 16k | 1 repos | ~847 | Automated safety check: Pass | MIT | |
| Create Pull Requestcline/cline | 70k | 1 repos | ~1.6k | Automated safety check: Pass | Apache-2.0 | |
| Pull Request Title and Body Writeropeninterpreter/openinterpreter | 69k | 2 repos | ~1.1k | Automated safety check: Pass | Apache-2.0 |
openinterpreter/openinterpreter
Watches an open GitHub pull request until it merges, handling review comments, diagnosing CI failures and retrying flaky checks along the way.
onyx-dot-app/onyx
Checks a GitHub, GitLab, or Perforce (p4) pull request (or merge request, or shelved changelist) for unresolved review comments, failing status checks, and incomplete PR descriptions.
HKUDS/OpenHarness
Merges external GitHub pull requests while keeping the original author credited, and fixes conflicts after the merge instead of rewriting the contribution.
cline/cline
Opens a GitHub pull request from your current branch with the gh CLI, after reviewing the commits and diff and gathering the details the PR needs.
openinterpreter/openinterpreter
Rewrites the title and body of one or more pull requests with gh, leading with why the change was made, then what changed, and describing only the net result.
prisma/orm
Runs a loop on a GitHub pull request: fetch review state, triage comments into actions, implement them and resolve threads, repeating until nothing actionable is left.
jggonz/os8088
Functionally verify a change on the glass before it merges - boot the built OS in QEMU, drive the actual UI the change proposes (mouse, keys, menus) over QMP, screenshot the evidence for every…
jggonz/os8088
Build a native 8086 assembly remake of a console/arcade game (reference = a disassembly or source tree) as an os8088 package, the way apps/drmario (DrMarco), apps/1942 and apps/excitebike were made…
jggonz/os8088
Port an existing program - written in C or in any other language - to os8088 as a C package (SPEC.md §73), the way apps/cword ported Microsoft Word 1.1a.
jggonz/os8088
Bring one of the maintainer's own stale pull requests (a branch on jggonz/os8088 that main has moved past) back to mergeable - merge main into it in a scratch worktree, decide whether it is still…
jggonz/os8088
Review an incoming pull request that comes from someone else's fork of os8088 - fetch it, merge main into it, review it with a team of agents for memory safety, lost-from-main regressions, redraw…
jggonz/os8088
Give an os8088 package a COLOUR FACE on VGA/EGA - fewer redraws first, then a neater layout, styled panes and bevelled, picture-faced buttons with their captions inside - while the Hercules and CGA…
Works with
Categories
Build os8088 and publish the floppy images to the os8088.com website repo as a pull request, plus a GitHub release on the OS repo. Release Os8088 is an agent skill from jggonz/os8088.com website repo as a pull request, plus a GitHub release on the OS repo.
Release Os8088 fits situations like: the user asks to cut a release; publish a new build; ship the latest images to the website; update os8088.com with a new version.
Run `npx skills add jggonz/os8088 --skill release-os8088 -a claude-code`. Or copy the skill folder (.claude/skills/release-os8088 in jggonz/os8088) into .claude/skills/release-os8088 in your project. Claude Code loads it when a task matches its description.
Run `npx skills add jggonz/os8088 --skill release-os8088 -a codex`. Or copy the skill folder (.claude/skills/release-os8088 in jggonz/os8088) into .agents/skills/release-os8088 in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add jggonz/os8088 --skill release-os8088 -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/release-os8088, .gemini/skills/release-os8088, .github/skills/release-os8088 and .opencode/skills/release-os8088 in your project.
Going by SKILL.md and its folder, Release Os8088 needs Python for the scripts in its folder and the command-line tools its instructions call (make, git, python3, gh, magick and curl). Our summary lists: Python 3.
SKILL.md names 2 domains. In commands or code: youtube.com and claude.com; the agent is likely to contact these when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Release Os8088 is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 10k tokens (SKILL.md is roughly 42k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Release Os8088: PR Babysitter (openinterpreter/openinterpreter, 69k stars), Check PR (onyx-dot-app/onyx, 32k stars), Contributor-First PR Merge (HKUDS/OpenHarness, 16k stars) and Create Pull Request (cline/cline, 70k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
jggonz (a GitHub user) maintains it in jggonz/os8088, which has 104 GitHub stars. The repository holds 7 skills in this directory. The repository was last updated on October 8, 2026.
Source: jggonz/os8088 on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.