Inspiration

What it does

How we built it

Challenges we ran intoInspiration

I am a Manual QA. Every release, the same ghosts come back: a console error nobody noticed, a 404 asset, an image with no alt text, a link to nowhere. They hide in plain sight until a user trips over them. Halloween gave me the metaphor — and Kiroween gave me the deadline. GhostQA is the QA agent I wished I had on every project: one that haunts the app for you and brings back the evidence.

What it does Paste any URL and hit Start the haunt. GhostQA crawls your site in headless Chromium (same-origin, configurable depth — up to 25 pages by default), and on every page it runs six checks:

Console errors — every console.error() the page throws Failed requests — 404s, 500s and dead assets (including HTTP error responses, not just network failures) Images without alt text and inputs without labels — accessibility poltergeists screen readers cannot see past Placeholder links — href="#" and javascript: links that go nowhere Mixed content — insecure http:// resources loaded on an https:// page You get a Haunt Report: every finding ranked by severity (critical / major / minor), with the page, the evidence, and a screenshot of the haunted page — plus a hauntedness score from 0–100 for the whole site, a copy-as-Markdown button to paste the report into a ticket, and an optional one-paragraph AI summary of the findings (bring your own free Gemini key; the app works fully without it and never blocks on it).

How I built it Spec-first, with Kiro. I wrote the requirements, design and task list as specs in .kiro/specs/ghostqa/, steering docs for product, tech and structure in .kiro/steering/, and an agent hook in .kiro/hooks/ — then built the whole app through Kiro CLI chat sessions (kiro-cli chat), with Kiro writing the code and me verifying every step independently: fresh-clone build, fixture site with four planted ghosts (the test asserts exactly the four planted check types, no extras), live haunts, and a real demo recording.

Stack: Node.js 24 + TypeScript, Express 5, Playwright (headless Chromium), vanilla HTML/CSS front-end with no build step and no framework. The optional AI summary calls Gemini (free tier) asynchronously in the background — the report returns in ~2 seconds and the summary attaches when it arrives, so a slow model can never block a haunt.

Challenges The duplicate console error. A planted 404 asset makes Chrome log "Failed to load resource" as a console error as well as a failed request — the same ghost wearing two sheets. I kept both: they are genuinely two signals, and deduping would lose real findings on real pages. The trailing-slash ghost. The crawler counted http://localhost:4000 and http://localhost:4000/ as two different pages and doubled every finding. Fixed with URL normalisation in the crawler, verified by a live haunt: 1 page, exactly 5 findings. Free-tier Gemini latency is unbounded. A trivial reply took 27.8 seconds; a real summary took over 120. The fix was architectural, not a bigger timeout: the summary now runs in the background and attaches to the stored report when it arrives. XSS in my own report page. The report embedded raw JSON in a script block — a finding containing a closing script tag (possible from a hostile target's console error) could break out. Now escaped, verified. Accomplishments that I'm proud of The fixture test passes end to end: exactly the four planted ghosts, no extras, hauntedness 55/100 — and the same result comes back from a fresh clone (git clone, npm ci, npm run build, haunt). Every finding ships with a screenshot as evidence, served from the report. The whole app is one small Express server with no paid APIs in the core path — the haunt works with zero keys, zero accounts, zero config. What I learned Spec-driven development with Kiro works when the specs are real: the requirements doc caught a 404-response gap before a line of crawler code was written, and the task list kept three Kiro sessions from stepping on each other. I also learned to verify, not trust: Kiro shipped the wrong Gemini model name (gemini-2.5-flash, which 404s) and I caught it by calling the API myself.

What's next More checks: slow loads, broken images, duplicate IDs, colour-contrast spot checks. Scheduled haunts: re-haunt a site nightly and diff the hauntedness score. A "most haunted page" leaderboard for large sites.

Accomplishments that we're proud of

What we learned

What's next for GhostQA — the ghost-hunter QA agent

https://github.com/Luando-tester/ghostqa https://ghostqa-production.up.railway.app/ https://youtu.be/e65H5g8PkrA

Built With

Share this project:

Updates

Submission history