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.
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
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
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
These rules shaped every suggestion:
- Kiro never used
anytypes - 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
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
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
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
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
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 { ... }
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']
}
})
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:
- Project Overview - What are you building? For whom?
- Technology Stack - What tools/frameworks are you using?
- Development Philosophy - What are your priorities and constraints?
- Implementation Preferences - What patterns should Kiro follow/avoid?
- 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" }
}
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}}" }
}
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" }
}
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"
}
}
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:
- Auto-run tests on save (catch bugs fast)
- Linting on save (maintain quality)
- 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:
- Define requirements - User stories with acceptance criteria
- Create a design - Architecture, components, data models
- Generate tasks - Actionable implementation checklist
- 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)