<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: lxivlog64</title>
    <description>The latest articles on DEV Community by lxivlog64 (@lxivlog64).</description>
    <link>https://dev.to/lxivlog64</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4223307%2F991846c4-dc18-4333-b871-f7d6ac6986db.png</url>
      <title>DEV Community: lxivlog64</title>
      <link>https://dev.to/lxivlog64</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lxivlog64"/>
    <language>en</language>
    <item>
      <title>Resurrecting RSS for Kiroween: Haunted Feeds, a spec-driven feed reader</title>
      <dc:creator>lxivlog64</dc:creator>
      <pubDate>Tue, 03 Nov 2026 01:52:20 +0000</pubDate>
      <link>https://dev.to/lxivlog64/resurrecting-rss-for-kiroween-haunted-feeds-a-spec-driven-feed-reader-2a37</link>
      <guid>https://dev.to/lxivlog64/resurrecting-rss-for-kiroween-haunted-feeds-a-spec-driven-feed-reader-2a37</guid>
      <description>&lt;p&gt;This post was written as part of the Kiroween 2026 hackathon.&lt;/p&gt;

&lt;p&gt;RSS never died. Podcasts still run on it, and most blogs still publish a feed. What went away was the habit of reading feeds. For Kiroween's &lt;strong&gt;Resurrection&lt;/strong&gt; category I built &lt;strong&gt;Haunted Feeds&lt;/strong&gt;, a small self-hosted reader that turns the sites you follow into one daily digest with short summaries, in a Halloween costume.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🎃 Live demo: &lt;a href="https://rss-resurrected.onrender.com" rel="noopener noreferrer"&gt;https://rss-resurrected.onrender.com&lt;/a&gt; (free tier, the first load can take ~50 s)&lt;/li&gt;
&lt;li&gt;🎥 Video (under 3 min): &lt;a href="https://youtu.be/kT7sllgMBfc" rel="noopener noreferrer"&gt;https://youtu.be/kT7sllgMBfc&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💾 Code (MIT): &lt;a href="https://github.com/lxivlog64/rss-resurrected" rel="noopener noreferrer"&gt;https://github.com/lxivlog64/rss-resurrected&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Follows RSS 2.0, RSS 1.0 and Atom feeds, imports and exports OPML&lt;/li&gt;
&lt;li&gt;Removes tracking parameters from links and de-duplicates items across polls&lt;/li&gt;
&lt;li&gt;Builds a digest for the last 24 hours, 3 days or a week, grouped by site&lt;/li&gt;
&lt;li&gt;Runs with &lt;strong&gt;no API key&lt;/strong&gt;: offline demo feeds and an extractive summarizer. Set &lt;code&gt;OPENAI_API_KEY&lt;/code&gt; to get LLM summaries, with automatic fallback if the API fails&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How the project is set up for Kiro
&lt;/h2&gt;

&lt;p&gt;Everything Kiro reads lives in the &lt;code&gt;.kiro/&lt;/code&gt; folder of the repo, in the formats described in the &lt;a href="https://kiro.dev/docs/" rel="noopener noreferrer"&gt;Kiro docs&lt;/a&gt;. If you open the repo in Kiro, this is what you get:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Steering&lt;/strong&gt; (&lt;a href="https://kiro.dev/docs/steering/" rel="noopener noreferrer"&gt;docs&lt;/a&gt;). &lt;code&gt;.kiro/steering/&lt;/code&gt; has the three foundation files Kiro defines: &lt;code&gt;product.md&lt;/code&gt; (goal, zero-key requirement, tone), &lt;code&gt;tech.md&lt;/code&gt; (Node 20, no build step, parsers must never throw, no secrets in code) and &lt;code&gt;structure.md&lt;/code&gt; (module map). &lt;code&gt;product.md&lt;/code&gt; and &lt;code&gt;tech.md&lt;/code&gt; use &lt;code&gt;inclusion: always&lt;/code&gt;; &lt;code&gt;structure.md&lt;/code&gt; uses &lt;code&gt;inclusion: fileMatch&lt;/code&gt; so it only loads when you work in &lt;code&gt;src/&lt;/code&gt; or &lt;code&gt;test/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Specs&lt;/strong&gt; (&lt;a href="https://kiro.dev/docs/specs/" rel="noopener noreferrer"&gt;docs&lt;/a&gt;). Each feature has the standard trio in &lt;code&gt;.kiro/specs/&amp;lt;feature&amp;gt;/&lt;/code&gt;: &lt;code&gt;requirements.md&lt;/code&gt; with user stories and EARS acceptance criteria, &lt;code&gt;design.md&lt;/code&gt;, and a &lt;code&gt;tasks.md&lt;/code&gt; checklist. There are three: &lt;code&gt;feed-ingestion&lt;/code&gt;, &lt;code&gt;digest-summaries&lt;/code&gt; and &lt;code&gt;opml-import&lt;/code&gt;. A typical criterion:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;WHEN a feed is re-polled, THE SYSTEM SHALL NOT store an item whose id already exists.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Writing the EARS lines first forced edge cases into the open: what counts as the same item, what happens with malformed XML (record an error, never throw), what to do with non-http URLs (HTTP 400). In Kiro, the tasks list in &lt;code&gt;tasks.md&lt;/code&gt; is what its task execution UI tracks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Correctness properties.&lt;/strong&gt; The feed-ingestion design lists properties that the code must satisfy, and &lt;code&gt;test/parser.test.js&lt;/code&gt; checks them with fast-check: de-duplication is idempotent and order-preserving, URL normalization is idempotent and ignores &lt;code&gt;utm_*&lt;/code&gt; and fragments, and the parser never throws on generated feeds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hooks&lt;/strong&gt; (&lt;a href="https://kiro.dev/docs/hooks/" rel="noopener noreferrer"&gt;docs&lt;/a&gt;). Two &lt;code&gt;v1&lt;/code&gt; hook files in &lt;code&gt;.kiro/hooks/&lt;/code&gt; use the &lt;code&gt;PostFileSave&lt;/code&gt; trigger. One runs &lt;code&gt;npm test&lt;/code&gt; when a file under &lt;code&gt;src/&lt;/code&gt; is saved; the other runs &lt;code&gt;node --check&lt;/code&gt; on the source modules when any &lt;code&gt;.js&lt;/code&gt; file is saved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP&lt;/strong&gt; (&lt;a href="https://kiro.dev/docs/mcp/configuration/" rel="noopener noreferrer"&gt;docs&lt;/a&gt;). &lt;code&gt;.kiro/settings/mcp.json&lt;/code&gt; is a workspace MCP config with the &lt;code&gt;fetch&lt;/code&gt; server (&lt;code&gt;uvx mcp-server-fetch&lt;/code&gt;), so the agent can pull a real feed when you ask it to look at one.&lt;/p&gt;

&lt;h2&gt;
  
  
  A real bug: escaped HTML vs. entity limits
&lt;/h2&gt;

&lt;p&gt;After deploying, the Rust blog's Atom feed (&lt;code&gt;https://blog.rust-lang.org/feed.xml&lt;/code&gt;) showed up as &lt;code&gt;invalid-xml&lt;/code&gt;, even though it is perfectly valid. The cause: fast-xml-parser counts every standard entity (&lt;code&gt;&amp;amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;amp;amp;&lt;/code&gt;…) toward a default limit of 1,000 expansions per document, a guard against "billion laughs" attacks. Blogs that put escaped HTML in &lt;code&gt;&amp;lt;content type="html"&amp;gt;&lt;/code&gt; easily go past that; this one had about 1,400. Standard entities can't recurse, so the fix raises only the total-expansion cap and keeps the DOCTYPE entity size and depth limits strict. A fixture with the same shape is now a regression test, which failed before the fix and passes after it. The steering rule "parsers must never throw" is why this surfaced as an error on the feed rather than a crash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/lxivlog64/rss-resurrected
&lt;span class="nb"&gt;cd &lt;/span&gt;rss-resurrected &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm start   &lt;span class="c"&gt;# http://localhost:3000&lt;/span&gt;
npm &lt;span class="nb"&gt;test&lt;/span&gt;                                          &lt;span class="c"&gt;# 10 tests incl. 3 property tests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or open &lt;a href="https://rss-resurrected.onrender.com" rel="noopener noreferrer"&gt;https://rss-resurrected.onrender.com&lt;/a&gt; and click &lt;strong&gt;👻 Load demo&lt;/strong&gt;. Happy Kiroween 🎃&lt;/p&gt;

&lt;h2&gt;
  
  
  Update: using Kiro on the project for real
&lt;/h2&gt;

&lt;p&gt;After the first version, I opened the repo in Kiro IDE 1.4.86 (Free plan) and used it on a new feature. Here is what happened, no polish.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spec mode.&lt;/strong&gt; I asked for a Markdown export of the digest. Kiro first added requirement 7 to &lt;code&gt;.kiro/specs/digest-summaries/requirements.md&lt;/code&gt;, in EARS form: &lt;em&gt;WHEN the user requests &lt;code&gt;GET /api/digest.md&lt;/code&gt;, THE SYSTEM SHALL return a Markdown document with content-type &lt;code&gt;text/markdown&lt;/code&gt;…&lt;/em&gt;. Then it added task 6 to &lt;code&gt;tasks.md&lt;/code&gt;, wrote &lt;code&gt;digestToMarkdown&lt;/code&gt; in &lt;code&gt;src/poller.js&lt;/code&gt;, added the route in &lt;code&gt;src/server.js&lt;/code&gt;, and added a test. It asked before running &lt;code&gt;npm test&lt;/code&gt;, which passed 12/12. One of its edits to &lt;code&gt;server.js&lt;/code&gt; broke the file, and it caught and fixed that itself. Total: about 4.4 credits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hooks.&lt;/strong&gt; Both hook files showed up in the Agent Hooks panel, and I enabled them there. When I saved a source file by hand, I didn't see the run-tests hook fire. I'm still looking into that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP.&lt;/strong&gt; The &lt;code&gt;fetch&lt;/code&gt; server connected with one tool, and Kiro did call &lt;code&gt;@fetch/fetch&lt;/code&gt; on a feed URL. The call was refused because DNS in my sandbox resolved the host to &lt;code&gt;198.18.0.1&lt;/code&gt;, a reserved address. So it's wired up, but it didn't fetch anything in my setup.&lt;/p&gt;

&lt;p&gt;The biggest change for me: I now write the requirement before the code, because that's what Kiro works from. Screenshots are in &lt;a href="https://github.com/lxivlog64/rss-resurrected/blob/main/docs/KIRO_USAGE.md" rel="noopener noreferrer"&gt;docs/KIRO_USAGE.md&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kiro</category>
      <category>hackathon</category>
      <category>rss</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
