For a Halloween-themed hackathon, I decided to build AI Psychic Hotline – a web app where you choose a “realm,” ask a question, the system draws tarot cards, and an AI “psychic” generates a themed
fortune that you can read (and optionally listen to) in a Halloween-inspired interface.
Instead of jumping straight into ad-hoc coding, I tried to use Kiro’s spec-driven workflow end to end: PRD → SRS → SDD → Kiro spec → implementation → quality checks.
This post walks through how I used Kiro’s features – spec-driven development, vibe coding, steering docs, hooks, and MCP – to keep the project structured and consistent.
Project images and links:
Starting with specs: PRD, SRS, SDD
Before writing code, I created three documents:
PRD (Product Requirements Document)
High-level goals: what the app should do, core flows, target users, and hackathon constraints.SRS (Software Requirements Specification)
Detailed functional and non-functional requirements, written using the EARS pattern and following INCOSE-style quality rules (clear, testable, focused requirements).SDD (Software Design Document)
Architecture and design: Next.js App Router, tarot data model, LLM integration, API routes, and error handling strategy.
Example EARS-style requirement from the SRS:
WHEN the user submits a valid question, THE SYSTEM SHALL generate a new reading by drawing 3–5 tarot cards and calling the LLM to produce a fortune based on the cards and the question.
Once these documents were in place, I fed them into Kiro.
From documents to Kiro spec
My first step with Kiro was spec-driven, not code-driven.
I asked it to:
“Take this PRD, SRS, and SDD for AI Psychic Hotline and turn them into a Kiro spec using your spec-driven development flow.”
Kiro created a project spec under:
.kiro/specs/ai-psychic-hotline/
requirements.md
design.md
tasks.md
requirements.md
Kiro combined the PRD and SRS into a requirements document with EARS-style requirements and user stories covering:
- Text-based question input
- Optional voice input (STT)
- Tarot spreads with three realms
- LLM-based fortune generation
- Optional TTS playback
- Movie-style or story-style recommendations aligned with the reading
- A simple “fate meter” interaction
- Privacy and error handling
design.md
It then turned the SDD into a design document that described:
- Next.js App Router architecture
- A
TarotDeckmodule andFortuneService - The
POST /api/fortunecontract (request and response structure) - Data models for cards and readings
- A service-layer approach (business logic outside React components)
- Error handling and basic security considerations
- Integration of a 3D fog background using something like Vanta.js
tasks.md
Finally, it generated a task list with a logical order:
- Project setup
- Tarot data and TypeScript types
- Backend services and endpoints
- UI components
- Optional voice features (STT/TTS)
- Optional “movie oracle” integration
- Visual and atmospheric polish
- Tests and documentation
This turned my initial documents into something Kiro could follow step by step.
Spec-driven development vs. vibe coding
Once the spec was ready, I used two different working styles with Kiro: strict, spec-driven implementation for core logic, and more relaxed “vibe coding” for UI and copy.
Spec-driven: backend and contracts
For backend code and data contracts, I stayed close to the spec. Examples of prompts:
- “Implement the
TarotDeckmodule andCardtype exactly as defined indesign.mdunder Data Model.” - “Create the
POST /api/fortuneendpoint according to the API contract in the design document.”
Kiro generated:
- TypeScript types for
CardandReadingResponse - A
TarotDeckthat loads card definitions and draws a small spread without duplicates -
A Next.js API route for
/api/fortunethat:- validates the question
- draws cards
- builds an LLM prompt from the cards and question
- returns
{ cards, fortune, movieRecommendation? }as JSON
Using the design document as the source of truth meant I didn’t have to keep re-explaining shapes and data; I just asked Kiro to “follow the spec.”
Vibe coding: UI, microcopy, and interactions
For the frontend and overall feel of the app, I used a more conversational, iterative approach:
- “Generate React components for the question input, tarot spread, and fortune view wired to
/api/fortune.” - “Write short, on-theme microcopy for labels, placeholders, and error messages.”
- “Add a simple card reveal animation that feels responsive but not distracting.”
- “Integrate a subtle 3D fog background behind the main panel.”
This “vibe coding” style worked well for layout, transitions, and tone. The spec ensured structure, while the conversation with Kiro let me experiment quickly with presentation and copy.
Steering docs: controlling style and behavior
Kiro has strong defaults (for example, it likes purple for UI). That didn’t match the Halloween palette I wanted, so I introduced steering docs.
UI steering
I created a colors and typography steering file that defined the palette and fonts. For example:
:root {
--background: #0a0a0a;
--foreground: #ededed;
--accent-primary: #f97316; /* orange */
--accent-secondary: #a3e635; /* green */
}
And I added explicit guidance:
- Don’t use purple/violet/fuchsia or Tailwind
purple-*/violet-*classes. - Use the accent colors above for highlights and borders.
- Use serif headings for titles and sans-serif for body text.
Whenever Kiro generated or refactored UI, I reminded it to follow this steering doc. That helped keep the visual language consistent: dark background, orange/green accents, and typography that fit the theme.
Persona steering
I also wrote a short steering doc for the “psychic” persona. Key points:
- Fortunes should be 3–6 sentences, within a reasonable word limit.
- They should reference the drawn cards by name.
- The tone should change slightly depending on the chosen realm (Love, Fate, Shadows).
- No literal claims about real-world outcomes (e.g., health, lottery, etc.).
Kiro then used this to shape the LLM system prompt and example outputs. This reduced the amount of manual tweaking I had to do to keep the “voice” consistent.
Engineering conventions
A third steering doc captured some simple engineering guidelines:
- Business logic should live in services, not React components.
- API contracts shouldn’t be changed casually; if they change, update the spec first.
- Use TypeScript strictly (no
anywhere a proper type is possible).
Together, these steering docs gave Kiro a clear set of constraints and preferences. The result was less drift in style and structure as the project evolved.
Agent hooks: automation for safety and feedback
As the project grew, I used agent hooks to have Kiro react to certain file changes automatically.
A typical pattern was:
-
Trigger: On save of files in
src/services/**or tarot-related modules. -
Action: Run tests and simple smoke tests that:
- call
/api/fortunewith a sample question - verify the response includes cards and a fortune in the expected shape
- call
This meant that when Kiro refactored FortuneService or tarot logic, the hook would automatically run checks and surface problems without me having to remember to run commands manually.
It’s not full-blown CI, but for a hackathon-scale project it was enough to catch obvious regressions early.
MCP tools: project-specific quality checks
Traditional tests tell you whether the code runs and returns the right structure. For this project, I also cared about more qualitative aspects: Are fortunes too long? Do they mention the cards? Are realm tones consistent?
To address this, I experimented with MCP tools as a kind of domain-specific quality layer:
- A tarot deck validator that checks
tarot.jsonfor missing data or inconsistencies. -
A fortune quality checker that:
- samples fortunes
- checks the length against persona guidelines
- looks for card names in the text
- evaluates basic tone consistency per realm
These tools flagged issues that unit tests didn’t. For example, an early version of the fortune generator produced text that was structurally correct but too long and verbose. Using MCP, I could measure average length and compliance with the 3–6 sentence rule, then adjust the prompt and token limits and re-check.
This made quality feel less subjective and provided feedback that fit the specific needs of this app.
Atmosphere and UX
Once the main functionality was in place, I used Kiro to help refine the user experience so it felt more like a focused “reading space” than a generic form:
- A realm selection step before the question, with short descriptions of Love, Fate, and Shadows.
- A simple question screen with on-theme copy (e.g., a more descriptive placeholder rather than just “Enter question”).
- A 3D-style fog background using a library like Vanta.js, tuned to stay subtle behind the main panel.
-
Small touches such as:
- “Shuffling the deck…” while the cards are being drawn
- “The spirits are silent. Try again in a moment.” for error states
- A basic “fate meter” interaction where the user can choose to accept or defy the reading
These are not complex features individually, but together they make the experience more coherent and intentional.
Takeaways
Working with Kiro on this project felt less like using a single “code generation button” and more like collaborating with a tool that understood structure, style, and workflow.
In practice, I used Kiro in a few distinct ways:
-
Spec-driven development: Turning PRD/SRS/SDD into
requirements.md,design.md, andtasks.mdand then implementing core logic from that spec. - Vibe coding: Iterating on UI components, microcopy, and subtle interactions through conversational prompts.
- Steering docs: Controlling visual design, tone of voice, and coding conventions so generated output stayed consistent.
- Agent hooks: Automating tests and smoke checks after certain changes, which made refactoring safer.
- MCP tools: Adding domain-specific quality checks that go beyond “does this compile” or “does this return JSON.”
For a hackathon project, this approach was surprisingly effective at keeping things organized and on-theme without losing the ability to iterate quickly.
If I repeat this pattern on another project, I’ll likely follow the same steps: start with lightweight specs, let Kiro build a project spec and tasks, add steering docs early, and then layer on hooks and MCP tools as the core features solidify.








Top comments (0)