Agent skill

Release Os8088

by jggonz in 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.

MITAuto-check passedDevelopment

Install Release Os8088

skills CLI
$ npx skills add jggonz/os8088 --skill release-os8088 -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install jggonz/os8088 release-os8088 --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ 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-src

Use ~/.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/

Facts

Skill name
release-os8088
GitHub stars
104
Token cost
~10k tokens
SKILL.md length
5,630 words
Files
3
Skills in repo
7
Repo updated
First seen
Licence
MIT

At a glance

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.

  • Works in 7 steps: Preflight → Build the images → Smoke-test the build before publishing → …
  • The user asks to cut a release
  • SKILL.md covers Writing the copy, Locating the two repositories, Arguments and Steps, plus 1 more section
  • Runs Python scripts from its folder; calls make, git and python3; reaches youtube.com and claude.com

What it does

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.

When your agent uses it

  • 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

Example prompts

  • “/release-os8088”

Requirements

  • Python 3

Workflow steps

7 steps, taken from the step headings in SKILL.md.

  1. Preflight
  2. Build the images
  3. Smoke-test the build before publishing
  4. Publish into the website repo
  5. Commit and open the pull request
  6. Tag and cut the GitHub release
  7. Report

What it can do on your machine

Read from SKILL.md and the folder at commit 95f7e97. It shows what the files ask for, not the result of running them.

  • Tool permissions

    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.

  • Runs code

    Ships script files (Python), which the agent can run.

    Shell commands in SKILL.md call:

    • make
    • git
    • python3
    • gh
    • magick
    • curl

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Hosts in commands or code, which the agent is likely to contact:

    • youtube.com
    • claude.com

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

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.

Always · name and description, kept in context so the agent knows when to use it
~72
When it runs · the whole SKILL.md, loaded when a task matches
~10k

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.

Safety

Auto-check passed

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.

SKILL.md

The full file from jggonz/os8088 at commit 95f7e97, republished under its MIT licence (© jggonz). 5,630 words, ~10,406 tokens.

Download SKILL.mdSave it as .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.
name
release-os8088
description
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.

Release os8088

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.

Writing the copy

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:

  1. Short sentences, one idea each. If a sentence needs a comma-spliced aside or a dash to hold together, it is two sentences.
  2. Explain the term the first time you use it, in the same sentence and in a few words: "Hercules, a monochrome graphics card from 1982", "the FAT12 filesystem DOS floppies use". Do this once per release, not once per highlight.
  3. No internal shorthand. A § 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.
  4. Numbers instead of adjectives. "Redraw dropped from 158 character cells a frame to 14" beats "much faster". If there is no measurement, say what changed and leave the speed claim out.
  5. No marketing. Cut "powerful", "seamless", "blazing", "beautiful", "exciting", "we're thrilled", "the best yet", "finally". No superlatives, no exclamation marks, no first-person plural selling the work. State the fact and stop.
  6. No fluff. Every sentence adds something a reader did not already have. Do not restate the title in the body. Do not open with throat-clearing ("As part of our ongoing work..."). Do not pad a small change into a paragraph -- a one-sentence highlight is a fine highlight.
  7. Leave out the war story unless it changes what someone does. The debugging that got you there is interesting to you and to nobody reading a download page.
  8. Plain words. "Faster" not "performant". "Uses less memory" not "optimises the footprint". "You can now" not "enables the ability to".

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:

fieldlength
summary2-4 sentences. What this release is, leading with the one thing that matters most.
highlights[].body2-5 sentences. One change each.
notes1-3 sentences, written as instructions to the reader.

Locating the two repositories

Never hardcode an absolute path. Resolve both from the current checkout, and use these variables in every command below:

bash
OS_REPO="$(git rev-parse --show-toplevel)"
WEB_REPO="${WEB_REPO:-$OS_REPO/../os8088-web}"
repowhat it isrole
$OS_REPOthis checkoutbuilds the images, gets the git tag and GitHub release
$WEB_REPOa sibling checkout of the websitereceives 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.

Arguments

  • $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.

Steps

1. Preflight

Run these and stop if any fails, reporting exactly what is wrong:

bash
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.

2. Build the images
bash
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.img

All 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:

  • It pairs with the SHIPPED apps.img. kern_emu holds the same API table at the same offsets, so there is no emu software disk and must not be.
  • It is 1.44MB only. The driver is 386 code, and no machine that needs a 360KB floppy can run it. The Makefile says a 720KB one is --size 720 if it is ever wanted; that is a Makefile change, not a release step.
  • Its kernel lives in 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:

bash
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 build

Offer 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.

3. Smoke-test the build before publishing

Never publish an image that has not been booted.

bash
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 ImageMagick

Look 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:

bash
python3 .claude/skills/release-os8088/emusmoke.py --shot build/smoke-emu.png

It 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:

bash
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.

3a. Pack the release zip

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.

bash
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:

  • Its file list is an allowlist, not a glob. 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.
  • It refuses to pack a partial release. A missing image that make or make emu builds stops it; a missing on-demand disk is reported and skipped.
  • The zip is byte-for-byte reproducible. Timestamps come from the date in the version string and the images are already deterministic, so the same version from the same commit packs to the same bytes. Do not add anything that varies per run.

Then look inside it, the same way step 3 makes you look at the screenshot:

bash
cd "$(mktemp -d)" && unzip -q "$OS_REPO/build/os8088-$VERSION.zip"
cd "os8088-$VERSION" && shasum -a 256 -c SHA256SUMS && cat README.md

Read 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.

4. Publish into the website repo
bash
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.

4a. Write the release notes into data/releases.json

This 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:

bash
# 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))
EOF

This 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:

bash
python3 tools/build.py        # must report 0 problems
python3 tools/linkcheck.py    # must report 0 dead
git status --porcelain

Look 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:

bash
(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
Show full SKILL.md (2,394 more words)Show less
4b. The Spotlight page, when the release earns one

/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:

bash
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 3

A 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:

mastheada .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-bandinverted full-width section heading, each with one line saying why you would read that section. These are the anchors somebody skims by
gallerythe screendumps in a plain .grid, two abreast
.spot-cardsa .grid of short titled cards, for what would otherwise be one window with four h3s in it
.spot-stepsnumbered 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:

bash
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:

  • Look at it at 1600px wide. A row of two screendumps needs a 1,360px viewport, so at anything narrower the page folds to one column and looks exactly like the thing this layout replaced. Check 390px too -- nothing may scroll sideways.
  • The stylesheet is cached and 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 video poster is a broken image locally and correct in production; see step 3.
4c. The Wire's library, every time

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:

bash
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.

5. Commit and open the pull request
bash
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)
6. Tag and cut the GitHub release

Only after the images are booted and the zip is unpacked and checked.

bash
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.

7. Report

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.

Rules

  • Never publish an unbooted image. Step 3 is not optional, and packing one into a zip does not make it booted.
  • The GitHub release gets the zip and nothing else. Never attach loose images beside it, and never build the zip from a glob of 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.
  • Never publish an emulator disk whose driver did not attach. emusmoke.py is how step 3 knows, and a desktop screenshot is not.
  • Never hand-assemble the zip. 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.
  • Never invent a checksum or a size. Everything on the download page comes from releases.json, which release.py computes from the actual bytes.
  • Never ship a release with an empty entry on the releases page. Step 4a is not optional either: the numbers are generated, the notes are not, and a version with nothing written against it is what an unfilled entry looks like to a reader.
  • Never ship copy that only a contributor can read. Spec section numbers, symbol names and register talk stay out of the notes, and so does marketing language. "Writing the copy" is the standard for the website entry, the GitHub release and any Spotlight page.
  • Never put a screenshot on a Spotlight page that you have not looked at. Step 4b captures them from a real boot and you read every one. A mockup, a crop of an old shot, or a scene whose click was lost are all the same defect to a reader: a picture of something that did not happen.
  • The licence is MIT -- 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.
  • Both repositories get branches, never direct commits to main.
  • If any step fails, stop and report rather than continuing with a partial release. A half-published release is worse than none.

© jggonz, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 2 other files in .claude/skills/release-os8088 of jggonz/os8088.

  • SKILL.md
  • emusmoke.py
  • mkzip.py

Open the folder on GitHubat commit 95f7e97

Compare with similar skills

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.

Release Os8088 compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Release Os8088 this skilljggonz/os8088104—~10kAutomated safety check: PassMIT
PR Babysitteropeninterpreter/openinterpreter69k3 repos~4.2kAutomated safety check: PassApache-2.0
Check PRonyx-dot-app/onyx32k2 repos~2.3kAutomated safety check: PassMIT
Contributor-First PR MergeHKUDS/OpenHarness16k1 repos~847Automated safety check: PassMIT
Create Pull Requestcline/cline70k1 repos~1.6kAutomated safety check: PassApache-2.0
Pull Request Title and Body Writeropeninterpreter/openinterpreter69k2 repos~1.1kAutomated safety check: PassApache-2.0

Similar skills

  • 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.

    69k GitHub starsUsed in 3 repos~4.2k tokens
    DevelopmentAuto-check passed
  • Check PR

    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.

    32k GitHub starsUsed in 2 repos~2.3k tokens
    DevelopmentAuto-check passed
  • Merges external GitHub pull requests while keeping the original author credited, and fixes conflicts after the merge instead of rewriting the contribution.

    16k GitHub starsUsed in 1 repo~847 tokens
    DevelopmentAuto-check passed
  • 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.

    70k GitHub starsUsed in 1 repo~1.6k tokens
    DevelopmentAuto-check passed
  • Pull Request Title and Body Writer

    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.

    69k GitHub starsUsed in 2 repos~1.1k tokens
    DevelopmentAuto-check passed
  • Official

    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.

    48k GitHub stars~2.2k tokensUpdated today
    DevelopmentAuto-check passed

More from jggonz/os8088

  • Functional Check

    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…

    104 GitHub stars~2.1k tokensUpdated 2 days ago
    Auto-check passed
  • Native Game Port

    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…

    104 GitHub stars~2.9k tokensUpdated 2 days ago
    Auto-check passed
  • Port To Os8088

    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.

    104 GitHub stars~3.6k tokensUpdated 2 days ago
    Auto-check passed
  • Refresh Stale PR

    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…

    104 GitHub stars~2.6k tokensUpdated 2 days ago
    Auto-check passed
  • Review Fork PR

    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…

    104 GitHub stars~4.3k tokensUpdated 2 days ago
    Auto-check passed
  • Vga Face

    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…

    104 GitHub stars~2.7k tokensUpdated 2 days ago
    Auto-check passed

Works with

Categories

Questions about Release Os8088

What does Release Os8088 do?

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.

When should I use Release Os8088?

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.

How do I install Release Os8088 in Claude Code?

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.

How do I install Release Os8088 in Codex?

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.

Can I use Release Os8088 in Cursor, Gemini CLI or GitHub Copilot?

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.

What does Release Os8088 need to run?

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.

Does Release Os8088 access the network?

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.

Is Release Os8088 safe to install?

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.

What licence does Release Os8088 use?

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.

How many tokens does Release Os8088 use?

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.

What are the alternatives to Release Os8088?

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.

Who maintains Release Os8088?

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.