I want to ask Claude to write a tiny OS with a tiny file system, with a tiny compiler and scripting language, all to be used in qemu/kvm or equivalent. the goal is that 100% of lines of code come from Claude. Make a plan.
agent · Jul 17, 18:56:23
A few architecture decisions will shape the plan significantly — let me pin those down first.
{
"questions": [
{
"question": "Target boot platform for QEMU/KVM?",
"header": "Boot target",
"options": [
{
"label": "x86 BIOS, custom boot sector (Recommended)",
"description": "16-bit real-mode boot sector we write ourselves, jump to 32-bit protected mode. Simplest to reason about, no GRUB/Multiboot dependency, classic 'tiny OS' path."
},
{
"label": "x86 Multiboot (GRUB)",
"description": "Kernel loaded via GRUB/Multiboot spec. Less boot-sector code to write, but pulls in a GRUB dependency and Multiboot header contract."
},
{
"label": "RISC-V (QEMU virt machine)",
"description": "Cleaner/more modern ISA, fewer legacy quirks (no real-mode/A20/segmentation), but less common tutorial ground and needs a RISC-V cross toolchain."
}
],
"multiSelect": false
},
{
"question": "Implementation language for the OS/kernel/compiler source?",
"header": "Source language",
"options": [
{
"label": "Assembly + freestanding C (Recommended)",
"description": "Boot sector and lowest-level entry in asm, rest of kernel/compiler/scripting engine in freestanding C. Best productivity-to-'tiny' ratio."
},
{
"label": "Pure assembly",
"description": "Everything hand-written in x86/RISC-V asm, no C at all. Maximally 'tiny' and dependency-free, but far more code and slower to build out a compiler on top of it."
}
],
"multiSelect": false
},
{
"question": "What counts as 'Claude writes 100% of the code' regarding the toolchain?",
"header": "Toolchain scope",
"options": [
{
"label": "Use existing assembler/linker/QEMU as tools (Recommended)",
"description": "nasm/gcc/ld/qemu are treated like a compiler is for any project — infrastructure, not authored code. Claude authors every line of the OS, filesystem, compiler, and scripting language itself."
},
{
"label": "Claude also writes the assembler/linker from scratch",
"description": "Much larger scope: a self-authored assembler and linker too, not just OS-level code. Significantly increases project size before any OS work starts."
}
],
"multiSelect": false
},
{
"question": "How minimal should the OS itself be (scheduling/memory/drivers)?",
"header": "OS scope",
"options": [
{
"label": "Single-tasking, polling drivers (Recommended)",
"description": "One running program at a time, no preemptive scheduler, simple bump/free-list allocator, polled keyboard + serial + framebuffer/VGA text output. Keeps focus on filesystem + compiler + scripting language."
},
{
"label": "Cooperative multitasking",
"description": "Simple round-robin cooperative scheduler with yield(), multiple 'processes' resident in memory. More OS-y, more code, more failure surface before the language layer is reached."
}
],
"multiSelect": false
}
]
}
result
Your questions have been answered: "Target boot platform for QEMU/KVM?"="x86 BIOS, custom boot sector (Recommended)", "Implementation language for the OS/kernel/compiler source?"="Assembly + freestanding C (Recommended)", "What counts as 'Claude writes 100% of the code' regarding the toolchain?"="Use existing assembler/linker/QEMU as tools (Recommended)", "How minimal should the OS itself be (scheduling/memory/drivers)?"="Single-tasking, polling drivers (Recommended)". You can now continue with these answers in mind.
Entered plan mode. You should now focus on exploring the codebase and designing an implementation approach.
In plan mode, you should:
1. Thoroughly explore the codebase to understand existing patterns
2. Identify similar features and architectural approaches
3. Consider multiple approaches and their trade-offs
4. Use AskUserQuestion if you need to clarify the approach
5. Design a concrete implementation strategy
6. When ready, use ExitPlanMode to present your plan for approval
Remember: DO NOT write or edit any files yet. This is a read-only exploration and planning phase.
total 48
drwxr-x--- 3 martin martin 4096 Jul 17 18:55 .
drwxr-x--- 220 martin martin 36864 Jul 17 18:54 ..
drwxr-x--- 8 martin martin 4096 Jul 17 18:55 .git
17c6619 init
# Tiny OS + tinyfs + tiny compiler/scripting language for QEMU/KVM
## Context
Goal: a from-scratch, minimal x86 OS that boots in QEMU/KVM, with its own tiny
filesystem, a tiny interpreted scripting language, and a tiny compiler (source
→ bytecode) with a bytecode VM to run it — all authored by Claude. Repo is
currently empty (just an `init` commit), so this is fully greenfield.
Confirmed decisions (from user):
- Boot: x86 BIOS, custom MBR boot sector (no GRUB/Multiboot).
- Language: assembly (bootloader, entry stubs) + freestanding C (everything else).
- Toolchain: nasm, gcc, ld, qemu are used as build infrastructure (like any
compiler) — not counted against "100% Claude-authored" code. Claude authors
every line of OS/fs/compiler/scripting source itself.
- OS model: single-tasking, no preemption, all drivers polled — no PIC/IDT-driven
interrupts, no scheduler, no process/syscall separation. Kernel is monolithic;
shell and interpreters run directly in kernel context. This keeps the OS layer
small so effort goes into the filesystem and language layers.
Toolchain verified present on this machine: `nasm 2.16`, `qemu-system-i386`,
`gcc 15.2` with working `-m32 -ffreestanding`, `ld 2.45`, `mtools`, `dd`. No
i686-elf cross-compiler needed — `gcc -m32 -ffreestanding -nostdlib` on the
host is sufficient, which avoids building/maintaining a cross toolchain.
Two distinct language layers, to make "compiler" and "scripting language"
genuinely separate deliverables rather than the same thing twice:
- **Scripting language**: dynamic, tree-walking interpreter, runs directly
from source each time (`run script.tsh`). Shell-automation flavored.
- **Tiny compiler**: a small statically-structured language compiled to a
custom stack-based bytecode, executed by an in-kernel bytecode VM
(`build prog.tc out.tbc` then `exec out.tbc`). Native x86 codegen is called
out as an optional stretch goal, not in the base plan — register allocation
and instruction selection would balloon scope for little benefit over a
bytecode VM given the "no process isolation" model.
## Repo layout
```
Makefile
README.md
.gitignore
boot/
boot.S stage1 MBR (512B): loads stage2 via BIOS INT13h, jumps to it
stage2.S stage2: enable A20, load kernel sectors, GDT, pmode switch, jump to kmain
kernel/
entry.S 32-bit entry stub -> calls kmain
kmain.c
gdt.c/.h
idt.c/.h minimal fault handlers only (divide-by-zero/GPF/PF -> print+halt), no IRQs
console.c/.h VGA text mode (0xB8000) driver
serial.c/.h 16550 UART driver (used for QEMU -serial stdio test output)
memory.c/.h free-list allocator over a fixed RAM region
string.c/.h freestanding libc subset (memcpy/memset/strlen/strcmp/itoa/...)
keyboard.c/.h polled PS/2 keyboard, scancode->ASCII (US subset)
ata.c/.h polled ATA PIO (LBA28) sector read/write
fs/
tinyfs.c/.h superblock+bitmap+inode table+data blocks; flat or 1-level dirs
shell/
shell.c/.h REPL: ls, cat, write, rm, run, build, exec, help, reboot, clear
lang/
lexer.c/.h shared tokenizer for both languages
script/
parser.c/.h ast.c/.h eval.c/.h scripting language front-end + tree-walk evaluator
compiler/
parser.c/.h codegen.c/.h tiny compiler front-end + bytecode codegen
vm.c/.h bytecode VM (stack machine, executes compiled programs)
tools/
mkfs_tinyfs.c host-side tool: formats disk image + seeds initial files at build time
run_tests.sh host-side: boots QEMU headless with scripted input, checks serial output
tests/
*.tsh scripting-language fixtures
*.tc compiler-language fixtures
expected/*.txt expected serial output per fixture
docs/
tinyfs.md on-disk format spec
bytecode-isa.md VM instruction set
scripting-language.md language reference
```
## Build phases (each independently buildable + testable in QEMU)
**Phase 0 — Scaffolding**
Makefile driving: nasm assembly, `gcc -m32 -ffreestanding -nostdlib -fno-pic`
compiles, `ld -m elf_i386` links with a custom linker script, `dd`/`mtools`
assembles the final disk image, `qemu-system-i386 -drive file=disk.img` boots
it. Minimal boot.S that prints one character via BIOS teletype and hangs.
Verify: boots in QEMU, prints the character, .gitignore covers build artifacts.
**Phase 1 — Bootloader**
Stage1 (512B, must end in 0x55AA): loads stage2 from disk via BIOS INT13h,
jumps to it. Stage2: enable A20 line, load kernel sectors into memory, build
a minimal GDT (flat code/data segments), switch to 32-bit protected mode,
far-jump into `kernel/entry.S` -> `kmain`.
Verify: kmain prints a banner string directly to VGA memory.
**Phase 2 — Kernel core**
Finalize GDT, add a minimal IDT with only exception handlers (no PIC
remapping, no IRQ handling — polling only) that print a diagnostic and halt.
console.c (VGA text), serial.c (UART, used as the machine-readable test
channel throughout), string.c, memory.c (free-list allocator over a fixed
known-size RAM region under QEMU's default map).
Verify: kmain runs a small self-test (alloc/free, string ops) and prints
pass/fail to serial; `run_tests.sh` checks for "PASS" on the serial line.
**Phase 3 — Disk + filesystem**
ata.c: polled PIO sector read/write. tinyfs: superblock + free-block bitmap +
fixed-size inode table + data blocks, flat namespace (or one level of
directories if it stays simple) — design documented in docs/tinyfs.md before
coding. fs/tinyfs.c: mount, list, create, read, write, delete.
tools/mkfs_tinyfs.c (host binary, built with the host's regular gcc, run at
image-build time) formats the disk image and seeds initial files/scripts.
Verify: kernel mounts the fs, lists seeded files, reads one back and compares
bytes against an expected constant, reports PASS/FAIL over serial.
**Phase 4 — Keyboard + shell**
keyboard.c: polled PS/2 driver + scancode table. Simple line editor
(printable chars, backspace, enter). shell/shell.c: `ls`, `cat`, `write`,
`rm`, `help`, `clear`, `reboot`.
Verify: `run_tests.sh` drives QEMU with scripted keystrokes (via QEMU's
`-serial stdio`, using stdin as a fake keyboard feed or the QEMU monitor's
`sendkey`) and asserts each shell command's expected serial output.
**Phase 5 — Scripting language**
lexer.c shared tokenizer. script/parser.c + ast.c build an AST for: ints,
strings, variables, if/while, function definitions/calls, `print`, and OS
builtins (open/read/write/list). script/eval.c tree-walks and executes it.
Shell gains `run <file>`.
Verify: mkfs seeds a `.tsh` fixture from tests/, `run_tests.sh` boots QEMU,
runs it via the shell, diffs serial output against tests/expected/.
**Phase 6 — Tiny compiler + bytecode VM**
docs/bytecode-isa.md defines a small stack-machine ISA (push/pop const,
arithmetic, jumps, calls, a handful of OS-facing "syscall" opcodes for I/O).
lang/compiler/{parser,codegen}.c compiles a minimal statically-structured
language (int vars, functions, if/while, arithmetic, builtin calls) to that
bytecode, reusing lexer.c. lang/vm.c executes bytecode. Shell gains
`build <src> <out>` and `exec <out>`.
Verify: mkfs seeds a `.tc` fixture, `run_tests.sh` boots QEMU, runs
`build`+`exec`, diffs serial output against expected.
**Phase 7 — Integration + docs**
One combined demo fixture exercising fs + scripting language + compiler
together. README.md with build/run instructions and an architecture
overview. Finish docs/*.md. Confirm `.gitignore` excludes all build
artifacts (*.o, *.bin, *.img, disk images) and history stays clean.
## Verification strategy
No hardware, no existing test framework — the serial port is the test
oracle throughout. `tools/run_tests.sh` boots
`qemu-system-i386 -display none -serial stdio -no-reboot -drive file=disk.img`,
feeds any needed scripted input, captures serial output, and diffs against
`tests/expected/*.txt`. This script is added in Phase 2 and reused/extended
by every later phase, giving each phase an automated pass/fail signal instead
of manual QEMU interaction.
## Sequencing note
Work proceeds phase by phase, each ending in a QEMU-verified checkpoint
before starting the next. This plan covers the full arc; implementation will
be done incrementally with a commit at the end of each phase.
result
File created successfully at: /home/martin/.claude/plans/swirling-mixing-bee.md (file state is current in your context — no need to Read it back)
{
"plan": "# Tiny OS + tinyfs + tiny compiler/scripting language for QEMU/KVM\n\n## Context\n\nGoal: a from-scratch, minimal x86 OS that boots in QEMU/KVM, with its own tiny\nfilesystem, a tiny interpreted scripting language, and a tiny compiler (source\n→ bytecode) with a bytecode VM to run it — all authored by Claude. Repo is\ncurrently empty (just an `init` commit), so this is fully greenfield.\n\nConfirmed decisions (from user):\n- Boot: x86 BIOS, custom MBR boot sector (no GRUB/Multiboot).\n- Language: assembly (bootloader, entry stubs) + freestanding C (everything else).\n- Toolchain: nasm, gcc, ld, qemu are used as build infrastructure (like any\n compiler) — not counted against \"100% Claude-authored\" code. Claude authors\n every line of OS/fs/compiler/scripting source itself.\n- OS model: single-tasking, no preemption, all drivers polled — no PIC/IDT-driven\n interrupts, no scheduler, no process/syscall separation. Kernel is monolithic;\n shell and interpreters run directly in kernel context. This keeps the OS layer\n small so effort goes into the filesystem and language layers.\n\nToolchain verified present on this machine: `nasm 2.16`, `qemu-system-i386`,\n`gcc 15.2` with working `-m32 -ffreestanding`, `ld 2.45`, `mtools`, `dd`. No\ni686-elf cross-compiler needed — `gcc -m32 -ffreestanding -nostdlib` on the\nhost is sufficient, which avoids building/maintaining a cross toolchain.\n\nTwo distinct language layers, to make \"compiler\" and \"scripting language\"\ngenuinely separate deliverables rather than the same thing twice:\n- **Scripting language**: dynamic, tree-walking interpreter, runs directly\n from source each time (`run script.tsh`). Shell-automation flavored.\n- **Tiny compiler**: a small statically-structured language compiled to a\n custom stack-based bytecode, executed by an in-kernel bytecode VM\n (`build prog.tc out.tbc` then `exec out.tbc`). Native x86 codegen is called\n out as an optional stretch goal, not in the base plan — register allocation\n and instruction selection would balloon scope for little benefit over a\n bytecode VM given the \"no process isolation\" model.\n\n## Repo layout\n\n```\nMakefile\nREADME.md\n.gitignore\nboot/\n boot.S stage1 MBR (512B): loads stage2 via BIOS INT13h, jumps to it\n stage2.S stage2: enable A20, load kernel sectors, GDT, pmode switch, jump to kmain\nkernel/\n entry.S 32-bit entry stub -> calls kmain\n kmain.c\n gdt.c/.h\n idt.c/.h minimal fault handlers only (divide-by-zero/GPF/PF -> print+halt), no IRQs\n console.c/.h VGA text mode (0xB8000) driver\n serial.c/.h 16550 UART driver (used for QEMU -serial stdio test output)\n memory.c/.h free-list allocator over a fixed RAM region\n string.c/.h freestanding libc subset (memcpy/memset/strlen/strcmp/itoa/...)\n keyboard.c/.h polled PS/2 keyboard, scancode->ASCII (US subset)\n ata.c/.h polled ATA PIO (LBA28) sector read/write\n fs/\n tinyfs.c/.h superblock+bitmap+inode table+data blocks; flat or 1-level dirs\n shell/\n shell.c/.h REPL: ls, cat, write, rm, run, build, exec, help, reboot, clear\n lang/\n lexer.c/.h shared tokenizer for both languages\n script/\n parser.c/.h ast.c/.h eval.c/.h scripting language front-end + tree-walk evaluator\n compiler/\n parser.c/.h codegen.c/.h tiny compiler front-end + bytecode codegen\n vm.c/.h bytecode VM (stack machine, executes compiled programs)\ntools/\n mkfs_tinyfs.c host-side tool: formats disk image + seeds initial files at build time\n run_tests.sh host-side: boots QEMU headless with scripted input, checks serial output\ntests/\n *.tsh scripting-language fixtures\n *.tc compiler-language fixtures\n expected/*.txt expected serial output per fixture\ndocs/\n tinyfs.md on-disk format spec\n bytecode-isa.md VM instruction set\n scripting-language.md language reference\n```\n\n## Build phases (each independently buildable + testable in QEMU)\n\n**Phase 0 — Scaffolding**\nMakefile driving: nasm assembly, `gcc -m32 -ffreestanding -nostdlib -fno-pic`\ncompiles, `ld -m elf_i386` links with a custom linker script, `dd`/`mtools`\nassembles the final disk image, `qemu-system-i386 -drive file=disk.img` boots\nit. Minimal boot.S that prints one character via BIOS teletype and hangs.\nVerify: boots in QEMU, prints the character, .gitignore covers build artifacts.\n\n**Phase 1 — Bootloader**\nStage1 (512B, must end in 0x55AA): loads stage2 from disk via BIOS INT13h,\njumps to it. Stage2: enable A20 line, load kernel sectors into memory, build\na minimal GDT (flat code/data segments), switch to 32-bit protected mode,\nfar-jump into `kernel/entry.S` -> `kmain`.\nVerify: kmain prints a banner string directly to VGA memory.\n\n**Phase 2 — Kernel core**\nFinalize GDT, add a minimal IDT with only exception handlers (no PIC\nremapping, no IRQ handling — polling only) that print a diagnostic and halt.\nconsole.c (VGA text), serial.c (UART, used as the machine-readable test\nchannel throughout), string.c, memory.c (free-list allocator over a fixed\nknown-size RAM region under QEMU's default map).\nVerify: kmain runs a small self-test (alloc/free, string ops) and prints\npass/fail to serial; `run_tests.sh` checks for \"PASS\" on the serial line.\n\n**Phase 3 — Disk + filesystem**\nata.c: polled PIO sector read/write. tinyfs: superblock + free-block bitmap +\nfixed-size inode table + data blocks, flat namespace (or one level of\ndirectories if it stays simple) — design documented in docs/tinyfs.md before\ncoding. fs/tinyfs.c: mount, list, create, read, write, delete.\ntools/mkfs_tinyfs.c (host binary, built with the host's regular gcc, run at\nimage-build time) formats the disk image and seeds initial files/scripts.\nVerify: kernel mounts the fs, lists seeded files, reads one back and compares\nbytes against an expected constant, reports PASS/FAIL over serial.\n\n**Phase 4 — Keyboard + shell**\nkeyboard.c: polled PS/2 driver + scancode table. Simple line editor\n(printable chars, backspace, enter). shell/shell.c: `ls`, `cat`, `write`,\n`rm`, `help`, `clear`, `reboot`.\nVerify: `run_tests.sh` drives QEMU with scripted keystrokes (via QEMU's\n`-serial stdio`, using stdin as a fake keyboard feed or the QEMU monitor's\n`sendkey`) and asserts each shell command's expected serial output.\n\n**Phase 5 — Scripting language**\nlexer.c shared tokenizer. script/parser.c + ast.c build an AST for: ints,\nstrings, variables, if/while, function definitions/calls, `print`, and OS\nbuiltins (open/read/write/list). script/eval.c tree-walks and executes it.\nShell gains `run <file>`.\nVerify: mkfs seeds a `.tsh` fixture from tests/, `run_tests.sh` boots QEMU,\nruns it via the shell, diffs serial output against tests/expected/.\n\n**Phase 6 — Tiny compiler + bytecode VM**\ndocs/bytecode-isa.md defines a small stack-machine ISA (push/pop const,\narithmetic, jumps, calls, a handful of OS-facing \"syscall\" opcodes for I/O).\nlang/compiler/{parser,codegen}.c compiles a minimal statically-structured\nlanguage (int vars, functions, if/while, arithmetic, builtin calls) to that\nbytecode, reusing lexer.c. lang/vm.c executes bytecode. Shell gains\n`build <src> <out>` and `exec <out>`.\nVerify: mkfs seeds a `.tc` fixture, `run_tests.sh` boots QEMU, runs\n`build`+`exec`, diffs serial output against expected.\n\n**Phase 7 — Integration + docs**\nOne combined demo fixture exercising fs + scripting language + compiler\ntogether. README.md with build/run instructions and an architecture\noverview. Finish docs/*.md. Confirm `.gitignore` excludes all build\nartifacts (*.o, *.bin, *.img, disk images) and history stays clean.\n\n## Verification strategy\n\nNo hardware, no existing test framework — the serial port is the test\noracle throughout. `tools/run_tests.sh` boots\n`qemu-system-i386 -display none -serial stdio -no-reboot -drive file=disk.img`,\nfeeds any needed scripted input, captures serial output, and diffs against\n`tests/expected/*.txt`. This script is added in Phase 2 and reused/extended\nby every later phase, giving each phase an automated pass/fail signal instead\nof manual QEMU interaction.\n\n## Sequencing note\n\nWork proceeds phase by phase, each ending in a QEMU-verified checkpoint\nbefore starting the next. This plan covers the full arc; implementation will\nbe done incrementally with a commit at the end of each phase.\n",
"planFilePath": "/home/martin/.claude/plans/swirling-mixing-bee.md"
}
result
169 lines
User has approved your plan. You can now start coding. Start with updating your todo list if applicable
Your plan has been saved to: /home/martin/.claude/plans/swirling-mixing-bee.md
You can refer back to it if needed during implementation.
## Approved Plan:
# Tiny OS + tinyfs + tiny compiler/scripting language for QEMU/KVM
## Context
Goal: a from-scratch, minimal x86 OS that boots in QEMU/KVM, with its own tiny
filesystem, a tiny interpreted scripting language, and a tiny compiler (source
→ bytecode) with a bytecode VM to run it — all authored by Claude. Repo is
currently empty (just an `init` commit), so this is fully greenfield.
Confirmed decisions (from user):
- Boot: x86 BIOS, custom MBR boot sector (no GRUB/Multiboot).
- Language: assembly (bootloader, entry stubs) + freestanding C (everything else).
- Toolchain: nasm, gcc, ld, qemu are used as build infrastructure (like any
compiler) — not counted against "100% Claude-authored" code. Claude authors
every line of OS/fs/compiler/scripting source itself.
- OS model: single-tasking, no preemption, all drivers polled — no PIC/IDT-driven
interrupts, no scheduler, no process/syscall separation. Kernel is monolithic;
shell and interpreters run directly in kernel context. This keeps the OS layer
small so effort goes into the filesystem and language layers.
Toolchain verified present on this machine: `nasm 2.16`, `qemu-system-i386`,
`gcc 15.2` with working `-m32 -ffreestanding`, `ld 2.45`, `mtools`, `dd`. No
i686-elf cross-compiler needed — `gcc -m32 -ffreestanding -nostdlib` on the
host is sufficient, which avoids building/maintaining a cross toolchain.
Two distinct language layers, to make "compiler" and "scripting language"
genuinely separate deliverables rather than the same thing twice:
- **Scripting language**: dynamic, tree-walking interpreter, runs directly
from source each time (`run script.tsh`). Shell-automation flavored.
- **Tiny compiler**: a small statically-structured language compiled to a
custom stack-based bytecode, executed by an in-kernel bytecode VM
(`build prog.tc out.tbc` then `exec out.tbc`). Native x86 codegen is called
out as an optional stretch goal, not in the base plan — register allocation
and instruction selection would balloon scope for little benefit over a
bytecode VM given the "no process isolation" model.
## Repo layout
```
Makefile
README.md
.gitignore
boot/
boot.S stage1 MBR (512B): loads stage2 via BIOS INT13h, jumps to it
stage2.S stage2: enable A20, load kernel sectors, GDT, pmode switch, jump to kmain
kernel/
entry.S 32-bit entry stub -> calls kmain
kmain.c
gdt.c/.h
idt.c/.h minimal fault handlers only (divide-by-zero/GPF/PF -> print+halt), no IRQs
console.c/.h VGA text mode (0xB8000) driver
serial.c/.h 16550 UART driver (used for QEMU -serial stdio test output)
memory.c/.h free-list allocator over a fixed RAM region
string.c/.h freestanding libc subset (memcpy/memset/strlen/strcmp/itoa/...)
keyboard.c/.h polled PS/2 keyboard, scancode->ASCII (US subset)
ata.c/.h polled ATA PIO (LBA28) sector read/write
fs/
tinyfs.c/.h superblock+bitmap+inode table+data blocks; flat or 1-level dirs
shell/
shell.c/.h REPL: ls, cat, write, rm, run, build, exec, help, reboot, clear
lang/
lexer.c/.h shared tokenizer for both languages
script/
parser.c/.h ast.c/.h eval.c/.h scripting language front-end + tree-walk evaluator
compiler/
parser.c/.h codegen.c/.h tiny compiler front-end + bytecode codegen
vm.c/.h bytecode VM (stack machine, executes compiled programs)
tools/
mkfs_tinyfs.c host-side tool: formats disk image + seeds initial files at build time
run_tests.sh host-side: boots QEMU headless with scripted input, checks serial output
tests/
*.tsh scripting-language fixtures
*.tc compiler-language fixtures
expected/*.txt expected serial output per fixture
docs/
tinyfs.md on-disk format spec
bytecode-isa.md VM instruction set
scripting-language.md language reference
```
## Build phases (each independently buildable + testable in QEMU)
**Phase 0 — Scaffolding**
Makefile driving: nasm assembly, `gcc -m32 -ffreestanding -nostdlib -fno-pic`
compiles, `ld -m elf_i386` links with a custom linker script, `dd`/`mtools`
assembles the final disk image, `qemu-system-i386 -drive file=disk.img` boots
it. Minimal boot.S that prints one character via BIOS teletype and hangs.
Verify: boots in QEMU, prints the character, .gitignore covers build artifacts.
**Phase 1 — Bootloader**
Stage1 (512B, must end in 0x55AA): loads stage2 from disk via BIOS INT13h,
jumps to it. Stage2: enable A20 line, load kernel sectors into memory, build
a minimal GDT (flat code/data segments), switch to 32-bit protected mode,
far-jump into `kernel/entry.S` -> `kmain`.
Verify: kmain prints a banner string directly to VGA memory.
**Phase 2 — Kernel core**
Finalize GDT, add a minimal IDT with only exception handlers (no PIC
remapping, no IRQ handling — polling only) that print a diagnostic and halt.
console.c (VGA text), serial.c (UART, used as the machine-readable test
channel throughout), string.c, memory.c (free-list allocator over a fixed
known-size RAM region under QEMU's default map).
Verify: kmain runs a small self-test (alloc/free, string ops) and prints
pass/fail to serial; `run_tests.sh` checks for "PASS" on the serial line.
**Phase 3 — Disk + filesystem**
ata.c: polled PIO sector read/write. tinyfs: superblock + free-block bitmap +
fixed-size inode table + data blocks, flat namespace (or one level of
directories if it stays simple) — design documented in docs/tinyfs.md before
coding. fs/tinyfs.c: mount, list, create, read, write, delete.
tools/mkfs_tinyfs.c (host binary, built with the host's regular gcc, run at
image-build time) formats the disk image and seeds initial files/scripts.
Verify: kernel mounts the fs, lists seeded files, reads one back and compares
bytes against an expected constant, reports PASS/FAIL over serial.
**Phase 4 — Keyboard + shell**
keyboard.c: polled PS/2 driver + scancode table. Simple line editor
(printable chars, backspace, enter). shell/shell.c: `ls`, `cat`, `write`,
`rm`, `help`, `clear`, `reboot`.
Verify: `run_tests.sh` drives QEMU with scripted keystrokes (via QEMU's
`-serial stdio`, using stdin as a fake keyboard feed or the QEMU monitor's
`sendkey`) and asserts each shell command's expected serial output.
**Phase 5 — Scripting language**
lexer.c shared tokenizer. script/parser.c + ast.c build an AST for: ints,
strings, variables, if/while, function definitions/calls, `print`, and OS
builtins (open/read/write/list). script/eval.c tree-walks and executes it.
Shell gains `run <file>`.
Verify: mkfs seeds a `.tsh` fixture from tests/, `run_tests.sh` boots QEMU,
runs it via the shell, diffs serial output against tests/expected/.
**Phase 6 — Tiny compiler + bytecode VM**
docs/bytecode-isa.md defines a small stack-machine ISA (push/pop const,
arithmetic, jumps, calls, a handful of OS-facing "syscall" opcodes for I/O).
lang/compiler/{parser,codegen}.c compiles a minimal statically-structured
language (int vars, functions, if/while, arithmetic, builtin calls) to that
bytecode, reusing lexer.c. lang/vm.c executes bytecode. Shell gains
`build <src> <out>` and `exec <out>`.
Verify: mkfs seeds a `.tc` fixture, `run_tests.sh` boots QEMU, runs
`build`+`exec`, diffs serial output against expected.
**Phase 7 — Integration + docs**
One combined demo fixture exercising fs + scripting language + compiler
together. README.md with build/run instructions and an architecture
overview. Finish docs/*.md. Confirm `.gitignore` excludes all build
artifacts (*.o, *.bin, *.img, disk images) and history stays clean.
## Verification strategy
No hardware, no existing test framework — the serial port is the test
oracle throughout. `tools/run_tests.sh` boots
`qemu-system-i386 -display none -serial stdio -no-reboot -drive file=disk.img`,
feeds any needed scripted input, captures serial output, and diffs against
`tests/expected/*.txt`. This script is added in Phase 2 and reused/extended
by every later phase, giving each phase an automated pass/fail signal instead
of manual QEMU interaction.
## Sequencing note
Work proceeds phase by phase, each ending in a QEMU-verified checkpoint
before starting the next. This plan covers the full arc; implementation will
be done incrementally with a commit at the end of each phase.
{
"subject": "Phase 0 — Scaffolding",
"description": "Makefile, dirs, minimal boot.S that prints a char and hangs, boots in QEMU, .gitignore",
"activeForm": "Setting up scaffolding"
}
result
Task #1 created successfully: Phase 0 — Scaffolding
; Stage 1 MBR boot sector.
; BIOS loads this at 0x7c00 and jumps to it in 16-bit real mode.
; For now: prove the boot path works by printing a character and halting.
; Stage 2 loading is added in Phase 1.
BITS 16
ORG 0x7c00
start:
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7c00
mov al, 'K'
mov ah, 0x0e ; BIOS teletype output
mov bh, 0
mov bl, 0x07
int 0x10
hang:
hlt
jmp hang
times 510-($-$$) db 0
dw 0xAA55
result
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/boot/boot.S (file state is current in your context — no need to Read it back)
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/Makefile (file state is current in your context — no need to Read it back)
agent · Jul 17, 19:04:09
I'll add a minimal serial-port write in the boot sector too, so Phase 0 is verifiable headlessly (VGA text mode isn't observable with `-display none`).
- mov al, 'K'
- mov ah, 0x0e ; BIOS teletype output
- mov bh, 0
- mov bl, 0x07
- int 0x10
-
- hang:
+ mov al, 'K'
+ mov ah, 0x0e ; BIOS teletype output
+ mov bh, 0
+ mov bl, 0x07
+ int 0x10
+
+ ; Also write to COM1 (0x3f8) so headless QEMU (-display none -serial stdio)
+ ; can observe this too. Raw port I/O here only; serial.c driver comes in Phase 2.
+ mov dx, 0x3f8 + 5 ; line status register
+ .wait_tx:
+ in al, dx
+ test al, 0x20 ; transmit holding register empty?
+ jz .wait_tx
+ mov dx, 0x3f8
+ mov al, 'K'
+ out dx, al
+
+ hang:
result
The file /home/martin/workspace/prototypes/boot-to-claude/boot/boot.S has been updated successfully. (file state is current in your context — no need to Read it back)
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/.gitignore (file state is current in your context — no need to Read it back)