DEV Community

Cover image for Building "Ghost in The Code" with Kiro: A Kiroween Hackathon Journey 👻

Building "Ghost in The Code" with Kiro: A Kiroween Hackathon Journey 👻

DEMO VIDEO HERE: https://youtu.be/W4fzNJCMGD8

As a contestant on Road to re:Invent, there’s one thing I know for sure: the hackathon is going to use Kiro. So when the Kiroween challenge appeared, I thought, perfect - this is how I get Kiro into my muscle memory before competition day.

One of my biggest passions is making tech fun and accessible for kids.

So naturally: Halloween + STEM = Ghost in The Code

A kid-friendly web game where players help a friendly ghost debug “haunted” code to save the digital world. Each spooky “bug” maps to a real programming concept (loops, conditionals, logic puzzles), and fixing it triggers a fun little animation.

But this wasn’t just a cute idea. It quickly turned into a very real engineering challenge. In a short amount of time, I needed to build:

  • Multiple interactive coding challenges across core concept types
  • A badge/achievement system
  • Smooth animations and visual feedback
  • AWS infrastructure for text-to-speech
  • Session-based progress tracking
  • A full TypeScript codebase with strict mode
  • All while trying to keep the game fun, polished, and kid-friendly

So yeah, this project had a _lot _ of moving parts.

And that’s exactly where Kiro came in.

How I used Kiro

Steering Kiro with Project Context

Before writing any code, I created a steering document. This is a markdown file that tells Kiro about my project's context, constraints, and preferences. This single file made every interaction with Kiro more productive (I am a busy person and only have a couple of hours at the end of each day to work on a project - I can't be wasting time telling Kiro not to do something 100 times.)

What's in My Steering Doc

I created .kiro/steering/project-context.md with five key sections:

1. Project Overview

An interactive web game where kids help a friendly ghost "debug" haunted code 
to save the digital world. Each bug represents a coding concept (loops, 
conditions, logic puzzles) with spooky animations as rewards.
Enter fullscreen mode Exit fullscreen mode

This one paragraph ensured Kiro always remembered:

  • Target audience: kids
  • Theme: friendly ghost, Halloween vibes
  • Educational goal: teach coding concepts

2. Technology Stack

- Frontend: Vite (for fast development and build)
- Infrastructure: AWS CDK (for deployment)
- Language: TypeScript (for all code where possible)
- Lambda Functions: TypeScript with Node.js 20.x runtime
Enter fullscreen mode Exit fullscreen mode

Kiro never suggested alternatives. No webpack. No CRA. No JavaScript. It stayed strictly within the stack.

3. Development Philosophy

This is a **hackathon project** with the following priorities:
- Focus on core functionality and user experience
- Code should be clean and understandable, but doesn't need to be production-grade
- Prioritize speed of development and demonstrable features
- It's okay to use simplified approaches and mock data where appropriate
Enter fullscreen mode Exit fullscreen mode

This was huge. Kiro understood I didn't need:

  • Exhaustive test coverage (just core functionality)
  • Enterprise-level error handling
  • Over-engineered abstractions
  • Perfect documentation

It optimized for speed and demonstrable features - exactly what a hackathon needs.

4. Implementation Preferences

- **Always use TypeScript** - All code should be written in TypeScript
- **Follow DRY** - Extract common logic into reusable components
- **No Docker** - Avoid Docker dependencies; use local bundling
- **Self-Documenting Code** - Clear naming, minimal comments
Enter fullscreen mode Exit fullscreen mode

These rules shaped every suggestion:

  • Kiro never used any types - always proper TypeScript
  • It extracted duplicate code into hooks and utilities automatically
  • It never suggested Docker for Lambda bundling
  • Comments only appeared for complex logic, not obvious code

5. TypeScript Guidelines

- Use TypeScript for all new code (frontend, backend, Lambda functions, CDK infrastructure)
- Enable strict mode in tsconfig.json
- Define proper interfaces and types for all data structures
- Use type guards for runtime type checking
- Avoid `any` type - use `unknown` or proper types instead
- Leverage TypeScript's type inference where appropriate
Enter fullscreen mode Exit fullscreen mode

Every file generated aligned with these constraints. Zero surprises.

Strategies That Made the Biggest Difference

1. Be Explicit About Constraints

Instead of: "Use TypeScript"
I wrote: "Always use TypeScript - All code should be written in TypeScript where possible for type safety"

The emphasis and explanation made Kiro prioritize this in every decision.

2. Define Your "Done" Criteria

Quality Standards:
- Tests should cover core functionality only - no need for exhaustive test coverage
- Error handling should be present but can be basic
- Performance optimization is secondary to getting features working
Enter fullscreen mode Exit fullscreen mode

This prevented Kiro from over-engineering. It knew when to stop.

3. State Your Anti-Patterns

- **No Docker** - Avoid Docker dependencies; use local bundling
- **Self-Documenting Code** - Avoid redundant comments
Enter fullscreen mode Exit fullscreen mode

Kiro actively avoided these patterns, instead of me having to correct it (over and over).

4. Include Context About Your Audience

Target Audience: Kids learning coding concepts
Enter fullscreen mode Exit fullscreen mode

This influenced:

  • Language in UI (simple, encouraging)
  • Error messages (friendly, not technical)
  • Educational content (age-appropriate)
  • Color choices (bright, engaging)

I think it really worked - my 7-year-old keeps wanting to play it.

5. Specify Your Tech Stack Upfront

Kiro never suggested alternatives or asked "should we use X or Y?" It knew the stack and worked within it.

Examples where I used Steering in the Project

Example 1: DRY Principle (Learned the Hard Way)

After building several similar components with repeated code, I realized Kiro wasn't automatically suggesting refactoring. So I added to my steering doc:

- **Follow DRY** - Extract common logic into reusable components
Enter fullscreen mode Exit fullscreen mode

Kiro started proactively refactoring patterns and even generated the usePersistence hook without being asked.

Lesson: If you see a repeated Kiro weakness, add a steering rule.

Example 2: TypeScript

Every generated file had:

interface Props {
  challenge: Challenge;
  onComplete: (success: boolean) => void;
}

// Never:
function handleSubmit(data: any) { ... }

// Always:
function handleSubmit(data: CodeSubmission): void { ... }
Enter fullscreen mode Exit fullscreen mode

Example 3: No Docker

When setting up Lambda bundling, Kiro used:

code: lambda.Code.fromAsset('lambda/polly', {
  bundling: {
    image: lambda.Runtime.NODEJS_20_X.bundlingImage,
    command: ['bash', '-c', 'npm install && cp -r . /asset-output']
  }
})
Enter fullscreen mode Exit fullscreen mode

Native CDK bundling (lightweight phew), no Docker required - exactly as specified. (Why I avoid Docker is another story for another time)

How to Create Effective Steering Docs

Start with these sections:

  1. Project Overview - What are you building? For whom?
  2. Technology Stack - What tools/frameworks are you using?
  3. Development Philosophy - What are your priorities and constraints?
  4. Implementation Preferences - What patterns should Kiro follow/avoid?
  5. Quality Standards - What does "done" look like?

Keep it concise:

  • My steering doc is ~50 lines
  • Each rule is 1-2 sentences
  • Use bold for emphasis on critical rules
  • Update as you learn what matters

Make it actionable:

  • Bad: "Write good code"
  • Good: "Follow DRY - Extract common logic into reusable components"

Include anti-patterns:

  • What should Kiro NOT do?
  • What tools should it avoid?
  • What's out of scope?

The ROI of Steering

  • 15 minutes writing the doc
  • Hours saved in corrections
  • Consistent TypeScript, architecture, decisions
  • No repeated reminders to Kiro
  • Lower cognitive load

Automating My Workflow with Agent Hooks

One of Kiro's most powerful features is agent hooks - automated workflows that trigger on specific events. I set up four hooks that dramatically improved my development velocity:

1. Auto-run Tests on Save

Every time I saved a TypeScript file, tests automatically ran. This gave me instant feedback:

{
  "name": "Auto-run Tests on Save",
  "when": { "type": "fileEdited", "patterns": ["**/*.ts", "**/*.tsx"] },
  "then": { "type": "runCommand", "command": "npm test -- --run" }
}
Enter fullscreen mode Exit fullscreen mode

Impact: Caught bugs within seconds of writing code. No more "I'll test it later" - testing became automatic.

2. ESLint on Save

Automatic linting kept my code quality high without thinking about it:

{
  "name": "ESLint on Save",
  "when": { "type": "fileEdited", "patterns": ["**/*.ts", "**/*.tsx"] },
  "then": { "type": "runCommand", "command": "npx eslint {{filePath}}" }
}
Enter fullscreen mode Exit fullscreen mode

Impact: Maintained consistent code style across the project. Fixed issues before they made it to commits.

3. Build Validation Check

When I modified core components or configuration, a build automatically triggered:

{
  "name": "Build Validation Check",
  "when": { 
    "type": "fileEdited", 
    "patterns": ["src/components/**/*", "src/engine/**/*", "vite.config.ts"]
  },
  "then": { "type": "runCommand", "command": "npm run build" }
}
Enter fullscreen mode Exit fullscreen mode

Impact: Caught breaking changes immediately. No more "it works on my machine" surprises.

4. CDK Synth Validation (The Smart One)

This hook was special - it triggered Kiro itself to validate infrastructure changes:

{
  "name": "CDK Synth Validation",
  "when": { "type": "fileEdited", "patterns": ["infrastructure/**/*.ts"] },
  "then": { 
    "type": "askAgent",
    "prompt": "Infrastructure code modified. Validate CDK synthesis and check for:
    1. Synthesis errors
    2. CloudFormation issues
    3. IAM policy problems
    4. Breaking changes"
  }
}
Enter fullscreen mode Exit fullscreen mode

Impact: Kiro would automatically run cdk synth, analyze the output, and tell me if my infrastructure changes were valid. It caught IAM permission issues and resource conflicts before I even tried to deploy.

The Compound Effect

These hooks created a continuous validation loop:

  • Write code → Auto-lint → Auto-test → Auto-build → Get feedback
  • All within seconds, without manual intervention

This was especially valuable during the hackathon when I was moving fast. The hooks acted as a safety net, catching issues immediately so I could maintain velocity without accumulating technical debt.

Setting Up Hooks

Creating hooks in Kiro is simple - just use the command palette and search for "Kiro Hook UI". You can:

  • Trigger on file saves, agent completions, or session starts
  • Run shell commands or ask Kiro to analyze something
  • Use file patterns to target specific parts of your codebase

For a hackathon project, I recommend starting with:

  1. Auto-run tests on save (catch bugs fast)
  2. Linting on save (maintain quality)
  3. Build validation for critical files (prevent breaking changes)

Spec-Driven Development: Structure Meets Speed

I started with spec-driven development to build the foundation, then switched to vibe coding once the specs were complete. This approach gave me the best of both worlds (in my humble opinion).

What is Spec-Driven Development?

Spec-driven development in Kiro is a structured workflow where you:

  1. Define requirements - User stories with acceptance criteria
  2. Create a design - Architecture, components, data models
  3. Generate tasks - Actionable implementation checklist
  4. Execute incrementally - Kiro implements one task at a time

My Actual Workflow: Spec First, Then Vibe

Phase 1: Spec-Driven Foundation (Days 1-2)

  • Core game engine and state management
  • AWS infrastructure with CDK
  • Challenge system and validation
  • Basic UI components
  • Progress tracking and persistence

Phase 2: Vibe Coding Extensions (Days 3-4)

  • Badge system enhancements
  • Animation polish and new effects
  • Voice narration with Polly
  • Accessibility improvements
  • UI refinements and styling
  • GitHub Actions CI/CD (added as a spec when needed)

The Main Game Spec

For the core game, I created a comprehensive spec with:

11 Requirements covering:

  • Game interface and UX
  • Challenge system
  • Animation feedback
  • Hint system
  • Progress tracking
  • Badge system
  • Accessibility features
  • Educator dashboard

42 Implementation Tasks including:

  • Core setup (CDK, Vite, React)
  • Game engine and state management
  • UI components
  • Animation system
  • Educational content
  • Accessibility implementation

Spec-Driven vs Vibe Coding: The Comparison

Aspect Spec-Driven Vibe Coding
Setup Time 15-30 min (requirements + design) Immediate
Clarity Crystal clear roadmap Figure it out as you go
Complexity Handles complex features well Best for simple tasks
Context Kiro maintains full context Can lose thread on complex work
Iteration Structured checkpoints Continuous refinement
Documentation Auto-generated Manual if needed
Best For Infrastructure, core systems UI, styling, quick fixes

Why This Workflow Worked Perfectly

Start with Spec = Solid Foundation

Creating the initial spec forced me to think through what features were actually needed, how components would interact, and where complexity would hide. The spec gave me a roadmap with no analysis paralysis and only a little architectural regret (I hate committing sometimes).

Switch to Vibe = Rapid Iteration

Once the foundation was solid, vibe coding let me experiment with animations, try different UI approaches quickly, and add "wow factor" features on the fly.

Around day 2, I had a working game from the spec. Then I switched to vibe coding and added particle burst animations, ghost STT with Polly, badge unlock celebrations, and accessibility options.

The Lesson: Spec the Foundation, Vibe the Features

Day 1: Create a comprehensive spec for your core system. Let Kiro generate the design and task list. Execute foundational tasks methodically to get a "working but basic" state.

Days 2-N: Switch to vibe coding for features, experiments, polish, and quick iterations.

When needed: If a vibe-coded feature gets complex, pause and create a mini-spec.

Starting with a spec prevents architectural walls and wasted refactoring time. Switching to vibe coding after the foundation lets you move fast and stay creative.

What About MCP (Model Context Protocol)?

Kiro supports MCP servers to extend its capabilities with external tools and data sources. I had it configured but honestly? I didn't use it much for this project.

The built-in features (code generation, file operations, AWS knowledge) covered 99% of what I needed. MCP is powerful for specialized workflows (like accessing proprietary APIs or custom data sources), but for a hackathon web game, the core Kiro features were more than enough.

When MCP might have helped:

  • If I needed real-time data from external APIs
  • If I was integrating with proprietary systems
  • If I needed specialized domain knowledge beyond AWS

Why I didn't need it:

  • Kiro's AWS knowledge was comprehensive
  • All my data was local (challenge JSON files)
  • The project was self-contained

MCP is there when you need it, but don't feel like you're missing out if you don't use it. Focus on mastering the core features first.

Final Thoughts

Kiroween challenged me to build something spooky, playful, and creative. Kiro made it possible to build something polished and educational in record time.

Whether you're working on a hackathon project, a side project, or something for production, a steering doc + hybrid development strategy is a game-changer.

Now if you'll excuse me, I have some haunted code to debug with my son... 👻✨

💻 View the code: https://github.com/ChaoticLabs/ghost-in-the-code

Top comments (0)