Bringing back the VCR board game with Kiro — a spec-driven build from the command line

Bringing back the VCR board game with Kiro — a spec-driven build from the command line

This post is written as part of the Kiroween 2026 hackathon. Our entry, VCR Party Host, is in the Resurrection category. In the early '90s, some board games came with a videotape. A host on the tape ran the evening, kept time, and every so often stopped the table cold. The format faded with the VCR. VCR Party Host brings it back as a web page you open on the TV: an on-screen host runs a four-player murder mystery at Quillmoor Manor, types out each clue, and interrupts you on a timer. ▶ Play it: https://ai-q-labs.github.io/vcr-party-host/ Code: https://github.com/ai-q-labs/vcr-party-host Video: https://youtu.be/vLg-ejj2Rg8 Who did what. Kiro CLI wrote the spec, the steering files, the hooks and the application code. Claude Code, an AI coding agent, wrote the one-page brief and every prompt, ran Kiro CLI, play-tested the app in Chrome and decided what to fix. The case and characters are original. This post is about how that worked from the command line, with Kiro's spec, steering and hooks, on the Free plan. 1. One page in, a whole spec out We started from a one-page BRIEF.md: the idea, what the app should do, and the constraints (original content only, English UI, static files on GitHub Pages). In a single non-interactive run, Kiro turned it into a feature spec under .kiro/specs/vcr-party-host/: requirements.md — 10 requirements, 45 acceptance criteria design.md — module layout, data model, test strategy tasks.md — 22 tasks in 8 phases; 18 of them name the acceptance criteria they satisfy That run cost 1.86 credits, read off the usage counter on the Kiro web settings page before and after. The spec changed how every later prompt looked. Instead of asking for a feature, each implementation prompt named tasks from tasks.md (later, the bug to fix), the acceptance criteria they had to meet and the tests that had to keep passing. One lesson came early: in one run Kiro misread the task numbers (it took T-04 to be the story-data task). From then on we put each task's full heading in the prompt, not just its ID. 2. Steering files that actually held Kiro wrote three steering files: product.md (who it is for, the host's tone, terse VCR labels like PLAY and DISMISS), tech.md (no build step, no runtime dependencies, no network requests at runtime, Node's built-in test runner only) and structure.md (which module owns what). We checked whether the constraints held rather than assuming it. In Chrome, the published app loads only its own files, and package.json has no dependencies. 3. Running Kiro non-interactively on the Free plan Everything ran through kiro-cli chat --no-interactive, one prompt per run, on the Kiro Free plan (50 credits a month). Each run got only the tools it needed through --trust-tools, and never --trust-all-tools, so Kiro could read and edit files but could not run shell commands. We ran the tests ourselves between runs. What it cost, measured on the same counter: spec: 1.86 credits steering files plus the first three tasks: 5.04 credits spec, 21 of the 22 tasks, the hooks and the first two bug fixes: 43.13 credits in total (the counter read 43.16; 0.03 of that was a connection test before the spec) Two things to plan for: Throttling. Free-plan runs sometimes stopped with The request was throttled by the service. After a throttled run we waited five minutes and sent it again. Model multipliers. --list-models shows a cost multiplier per model. auto is 1.0 and Claude Haiku 4.5 is 0.4, so small, well-specified fixes went to Haiku. 4. Hooks: make sure they fire where you work We set up three hooks in .kiro/hooks/: run-tests — on PostFileSave under src/ or tests/, runs the test suite original-content-guard — on saving data/story.js, asks Kiro to look for real people, brands and existing works in the story spec-sync — on PostTaskExec, checks a finished task's acceptance criteria against the tests Kiro's first attempt put the hooks in .kiro/agents/ and called a CLI option that does not exist. We read the hooks documentation and had Kiro rewrite them as standalone .kiro/hooks/*.json files. Then the hooks did not seem to fire, and the cause was ours. With the V3 agent (--v3), --trust-tools matches V3 tool IDs exactly. We had been passing fs_read,fs_write and then read,write. In V3, editing an existing file is str_replace (fs_write only creates or overwrites a whole file), so Kiro's edits to existing files were denied and we could not tell whether a hook had run. With read_file,str_replace, the edits went through, and the hooks fired: after Kiro edited src/timer.js, run-tests ran the suite (for the test we pointed its output at a log file: 131 tests, 131 passing); after a save of story.js, original-content-guard ran and reported no matches. We still searched every name ourselves. A detail that saves time if you write your own matchers: a hook receives the event on stdin, and file_path is relative to the project folder (for example src/timer.js), so a matcher like ^(src|tests)/.*\.js$ works as written. One hook we never saw fire: in the CLI, PostTaskExec comes from /spec run in the interactive UI, and we ran Kiro non-interactively. That is a real gap in our setup, and we say so in the submission. 5. What only the browser found Tests passed long before the app worked. These bugs only showed up when the app was played in a real browser, and each one went back into tasks.md with its cause and fix, plus a regression test written by Kiro: Nothing appeared after PLAY: an inline display:none on four of the five screens beat the .active class. Round 1's title and clue were empty: the render guards did not fire when the game first started. After STOP, the title screen kept the old elapsed time. Ending a round on its first clue skipped the next round's first clue. We caught this one on contact sheets of the demo video, one frame every four seconds. Reading the timing code against the acceptance criteria found four more. An interruption that arrived mid-typing left the clue cut off after DISMISS; the clock and the other interruption timers kept running behind the alert; pausing twice in one round made a 30-second interruption fire at 40 seconds; and after REW the clock kept running into the next game. Kiro fixed all four, each with a regression test that fails on the old code, and we replayed them in headless Chromium with a controlled clock (14 of 14 checks on the fixed build, locally and on GitHub Pages; the same script fails all four on the old build). That round taught us one more thing. Kiro had no shell, so it could not run the tests it wrote, and it reported success while four tests failed: three were mistakes in the test code, one was a real bug in the new pause logic. Kiro fixed the bug and we repaired the tests. The same round also deleted one existing test by mistake; we caught it in review and Kiro put it back. The suite now has 145 tests. The spec also made a promise the platform could not keep. It promised the app would run when opened straight from disk, but browsers block ES module scripts from file://. We confirmed that in headless Chrome and changed the acceptance criterion to require a static HTTP host, rather than bending the code to fit a wrong promise. One more catch that was not a bug: the manor's first name, which Kiro picked, turned out to be the title of an existing game. We renamed it to Quillmoor across six files before publishing. Generated names need the same search as any other name. What we would tell the next team Treat the spec as a ledger. When every bug and every wrong assumption goes back into tasks.md, the next run starts from the record, not from a chat log. Put the task heading in the prompt, not just the ID. Check that each hook fires on the surface you use. A hook that never runs looks exactly like a hook that finds nothing. Compare the screen before and after each step in a run-through. "The screen changed" is not the same as "the right thing is on the screen". Run the tests yourself after every agent run if the agent has no shell, and check that each new regression test fails on the old code. "Done" from an agent that could not run anything means "edited", nothing more. Thanks to the Kiro team for running Kiroween, and to everyone who still has a VCR in the cupboard.

Original Source

Read the full article at Dev →

KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.