<?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: Rahul R M</title>
    <description>The latest articles on DEV Community by Rahul R M (@cryptomanrahul).</description>
    <link>https://dev.to/cryptomanrahul</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%2F1920772%2F13d75fd2-9989-4b28-8c9d-8b36eb7d98d7.png</url>
      <title>DEV Community: Rahul R M</title>
      <link>https://dev.to/cryptomanrahul</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cryptomanrahul"/>
    <language>en</language>
    <item>
      <title>I Self-Hosted SigNoz for a Hackathon — Here's What the Docs Don't Cover</title>
      <dc:creator>Rahul R M</dc:creator>
      <pubDate>Sun, 19 Jul 2026 17:05:28 +0000</pubDate>
      <link>https://dev.to/cryptomanrahul/i-self-hosted-signoz-the-week-its-docker-compose-died-heres-the-map-2o1n</link>
      <guid>https://dev.to/cryptomanrahul/i-self-hosted-signoz-the-week-its-docker-compose-died-heres-the-map-2o1n</guid>
      <description>&lt;p&gt;I sat down on a Saturday to self-host SigNoz for the Agents of SigNoz hackathon. The &lt;a href="https://signoz.io/docs/install/docker/" rel="noopener noreferrer"&gt;official Docker guide&lt;/a&gt; is clear: install &lt;code&gt;foundryctl&lt;/code&gt;, write a &lt;code&gt;casting.yaml&lt;/code&gt;, run &lt;code&gt;foundryctl cast&lt;/code&gt;. Three commands, containers up, UI on &lt;code&gt;:8080&lt;/code&gt;. Done.&lt;/p&gt;

&lt;p&gt;Except not done. The docs get SigNoz running. They don't get a four-service app &lt;em&gt;into&lt;/em&gt; it, don't mention that telemetry sits idle until first-boot setup finishes, and don't cover provisioning alerts and dashboards through the API. Stack Overflow and older blog posts still point at &lt;code&gt;./install.sh&lt;/code&gt; in the SigNoz repo's &lt;code&gt;deploy/&lt;/code&gt; folder — that path was &lt;a href="https://github.com/SigNoz/signoz/releases/tag/v0.130.0" rel="noopener noreferrer"&gt;deprecated in v0.130.0&lt;/a&gt;. If you land there first, you'll think Docker is broken. It isn't; the old bundled compose is just gone.&lt;/p&gt;

&lt;p&gt;This post is the map I wish I'd had after &lt;code&gt;docker ps&lt;/code&gt; looked healthy: Foundry with a multi-service Docker network, wiring OTLP from real apps, and alerts/dashboards as code — plus three gotchas that cost me real hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Foundry: what the install guide actually gives you
&lt;/h2&gt;

&lt;p&gt;Foundry is SigNoz's install-as-code tool. You declare what you want in &lt;code&gt;casting.yaml&lt;/code&gt;; Foundry renders Docker artifacts and applies them. The happy path from the docs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://signoz.io/foundry.sh | bash   &lt;span class="c"&gt;# install foundryctl&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# casting.yaml&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Installation&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;signoz&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deployment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;flavor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;compose&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;foundryctl cast &lt;span class="nt"&gt;-f&lt;/span&gt; casting.yaml   &lt;span class="c"&gt;# gauge → forge → start containers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;cast&lt;/code&gt; writes a &lt;code&gt;pours/deployment/&lt;/code&gt; directory and brings the stack up. Inside you'll find a plain, readable &lt;code&gt;compose.yaml&lt;/code&gt; — ClickHouse, ClickHouse Keeper, Postgres metastore, the SigNoz server, and the OTel ingester. That's the part I like most: generated infrastructure you can commit to git, diff on upgrades, and run yourself with &lt;code&gt;docker compose up -d&lt;/code&gt; if you prefer manual control (&lt;code&gt;foundryctl forge&lt;/code&gt; renders without starting).&lt;/p&gt;

&lt;p&gt;One wrinkle when wiring other containers into the same network: the generated collector DNS alias is &lt;code&gt;signoz-ingester&lt;/code&gt;, while every OpenTelemetry tutorial (and my app configs) expect &lt;code&gt;signoz-otel-collector&lt;/code&gt;. Foundry lets you patch the rendered compose declaratively instead of hand-editing &lt;code&gt;pours/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;patches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deployment/compose.yaml"&lt;/span&gt;
      &lt;span class="na"&gt;operations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;op&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;add&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/services/ingester/networks/signoz-network/aliases/-&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;signoz-otel-collector&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;op&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;replace&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/services/signoz-signoz-0/networks&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;signoz-network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;aliases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;signoz&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Re-run &lt;code&gt;cast&lt;/code&gt; (or &lt;code&gt;forge&lt;/code&gt; if you're managing compose yourself), and both hostnames resolve. My four FastAPI services (gateway → checkout → payment → inventory, plus a load generator) point &lt;code&gt;OTEL_EXPORTER_OTLP_ENDPOINT&lt;/code&gt; at &lt;code&gt;http://signoz-otel-collector:4317&lt;/code&gt; and never know anything changed.&lt;br&gt;
&lt;strong&gt;Gotcha #1: no telemetry flows until you finish first-boot setup.&lt;/strong&gt; My services were up, exporting OTLP, zero errors — and ClickHouse stayed empty. The collector's logs showed OpAMP connection errors. Turns out the ingester gets its live config from the SigNoz server over OpAMP, and that only starts working after the initial admin account is created (&lt;code&gt;/api/v1/version&lt;/code&gt; shows &lt;code&gt;"setupCompleted": false&lt;/code&gt; until then). Create the account, and within a minute spans started landing. If you're staring at an empty Services page with healthy containers, check this first.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7dwr9c5ibg7rages7d7j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7dwr9c5ibg7rages7d7j.png" alt="SCREENSHOT: Services page showing gateway/checkout/payment/inventory with latency + error rate columns" width="799" height="367"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  My favorite feature: Query Builder v5 is a real query language now
&lt;/h2&gt;

&lt;p&gt;The thing that made me stay up late wasn't the install — it was the new &lt;strong&gt;v5 filter expressions&lt;/strong&gt;. Filters are no longer stacks of key/operator/value dropdowns; they're strings you type, like SQL's WHERE clause but attribute-aware:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'checkout'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;has_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aggregations are expressions too — &lt;code&gt;count()&lt;/code&gt;, &lt;code&gt;p99(duration_nano)&lt;/code&gt;, &lt;code&gt;count_distinct(user.id)&lt;/code&gt;. The combination that sold me completely — comparing a rollout by grouping error counts by &lt;code&gt;service.version&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'checkout'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;has_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
         &lt;span class="k"&gt;group&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During a simulated bad deploy, this splits cleanly: v1.4.2 carrying all the errors, v1.4.1 flat at zero. One query answers "did the deploy break it?" (The API layer goes further than the explorer dropdowns expose — alert rules accept conditional aggregation expressions like &lt;code&gt;countIf(has_error = true)&lt;/code&gt; directly, which I use below.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F30c74o0j4tzvm1xu3ylz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F30c74o0j4tzvm1xu3ylz.png" alt="SCREENSHOT: Traces explorer, group by service.version, showing errors concentrated on 1.4.2" width="800" height="658"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Log search gets the same language, including regex. This finds AWS-style access keys leaking into production logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="n"&gt;REGEXP&lt;/span&gt; &lt;span class="s1"&gt;'AKIA[0-9A-Z]{16}'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;deployment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'production'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I turned exactly that expression into an alert rule — a secret-leak tripwire in one line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gotcha #2: resource attributes shadow span attributes in group-bys.&lt;/strong&gt; My checkout service models deploys dynamically — the "deployed version" changes at runtime without a container restart, stamped on each span as a &lt;code&gt;service.version&lt;/code&gt; span attribute. But my group-by kept showing the &lt;em&gt;old&lt;/em&gt; version for every span, errors included. The reason: I also had &lt;code&gt;service.version=1.4.1&lt;/code&gt; baked into &lt;code&gt;OTEL_RESOURCE_ATTRIBUTES&lt;/code&gt;, and when both a resource attribute and a span attribute share a name, the resource wins in queries. The fix: pick one owner for the attribute. I removed it from the resource env and stamped it per-request in an ASGI middleware:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;stamp_service_version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call_next&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_current_span&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_recording&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;service.version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;current_version&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;call_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even after the fix there's a second layer: if a key has &lt;em&gt;ever&lt;/em&gt; existed in both contexts, SigNoz resolves the bare name to the &lt;strong&gt;resource&lt;/strong&gt; context by default — my group-by silently returned empty versions until I read the warning attached to the query response: &lt;em&gt;"Key 'service.version' is ambiguous… Using 'resource' context by default. To query attributes explicitly, use the fully qualified name (e.g., 'attribute.service.version')."&lt;/em&gt; Qualified name in the group-by, and rollout comparison worked exactly as advertised. Read the warnings SigNoz attaches to results — every one I hit contained the exact fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alerts and dashboards as code (against the real API)
&lt;/h2&gt;

&lt;p&gt;I wanted the whole setup reproducible from a fresh clone, so no clicking around the UI: notification channels, five alert rules, and three dashboards all provisioned by a Python script hitting the REST API. Three things I learned that aren't in any tutorial yet:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alert rules want the v5 shape.&lt;/strong&gt; My first attempt used the older &lt;code&gt;builderQueries&lt;/code&gt; JSON — rejected with &lt;code&gt;"alert rule is not valid"&lt;/code&gt;. The current schema mirrors what the UI sends: &lt;code&gt;"version": "v5"&lt;/code&gt; and a &lt;code&gt;queries&lt;/code&gt; array where each entry has a &lt;code&gt;spec&lt;/code&gt; with &lt;code&gt;signal&lt;/code&gt;, &lt;code&gt;aggregations&lt;/code&gt; (expression strings), and a &lt;code&gt;filter&lt;/code&gt; expression:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"alert"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"checkout-error-rate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"alertType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TRACES_BASED_ALERT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ruleType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"threshold_rule"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"v5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evalWindow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1m"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"compositeQuery"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"queryType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"builder"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"queries"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"builder_query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"spec"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"signal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"traces"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"aggregations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"expression"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"countIf(has_error = true)"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"filter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"expression"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"service.name = 'checkout'"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"above"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"matchType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"at_least_once"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"preferredChannels"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"agent-k-webhook"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note &lt;code&gt;preferredChannels&lt;/code&gt; takes channel &lt;em&gt;names&lt;/em&gt;, not IDs — the server folds them into the rule's notification thresholds itself. And webhook channels follow the upstream Alertmanager schema: the key is &lt;code&gt;url&lt;/code&gt;, not &lt;code&gt;api_url&lt;/code&gt; (the error message &lt;code&gt;one of url or url_file must be configured&lt;/code&gt; finally tipped me off).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gotcha #3: &lt;code&gt;POST /api/v1/dashboards&lt;/code&gt; wants the dashboard content directly.&lt;/strong&gt; I exported a dashboard, saved it as &lt;code&gt;{"data": {...}}&lt;/code&gt;, and POSTed the whole file. The API happily returned success — and stored a broken, title-less dashboard with my payload double-nested inside. No error, just quiet corruption. Unwrap the file and send the inner object (title, widgets, …). If your dashboard list ever shows untitled entries, this is why.&lt;/p&gt;

&lt;p&gt;The payoff: &lt;code&gt;make provision&lt;/code&gt; takes a virgin SigNoz to fully-armed — channels, alerts, dashboards — in about four seconds, idempotently. Trigger a bad deploy in the sandbox and the checkout-error-rate alert fires and webhooks out about three minutes later.&lt;/p&gt;

&lt;p&gt;Why three minutes and not one? I stared at "inactive" rules long enough to go read the server logs: every evaluation logs &lt;code&gt;eval_delay: 120000&lt;/code&gt;. Threshold rules deliberately evaluate a window ending &lt;strong&gt;two minutes in the past&lt;/strong&gt;, so late-arriving spans can't cause false negatives — my 1-minute window at 11:08 was scoring traffic from 11:05–11:06, before the incident started. Once you know that, "my alert is slow" becomes "my alert is correct"; budget for &lt;code&gt;eval window + 2m&lt;/code&gt; in any latency math you do.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F571ubbpj6aekueikugh1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F571ubbpj6aekueikugh1.png" alt="SCREENSHOT: Alerts page with the five provisioned rules, checkout-error-rate in FIRING state" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9du4z2iix0gpdltudloo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9du4z2iix0gpdltudloo.png" alt="SCREENSHOT: one provisioned dashboard with panels populated" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell my past self
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Don't fight the migration — use it.&lt;/strong&gt; Foundry's generated compose is more readable than the old monolithic file ever was, and committing &lt;code&gt;pours/&lt;/code&gt; gives you reviewable infra.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Empty telemetry ≠ broken exporter.&lt;/strong&gt; Check &lt;code&gt;setupCompleted&lt;/code&gt; and the OpAMP logs before touching your app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One owner per attribute.&lt;/strong&gt; Resource attrs are for static facts; anything dynamic belongs on spans — and never both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The API errors are good.&lt;/strong&gt; Every schema mistake I made produced a message that, read carefully, contained the fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expressions &amp;gt; dropdowns.&lt;/strong&gt; v5 filter syntax is the first query builder I've used where the "builder" part never got in my way.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything above ran on my machine this week — SigNoz v0.133, Foundry v0.2.14, Python 3.12 services on Docker. Next week is the Agents of SigNoz hackathon; this stack is the foundation I'll be building on, and now you can stand it up in an afternoon instead of a weekend.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Docs: &lt;a href="https://signoz.io/docs/install/docker/" rel="noopener noreferrer"&gt;signoz.io/docs/install/docker&lt;/a&gt; · Foundry: &lt;a href="https://github.com/SigNoz/foundry" rel="noopener noreferrer"&gt;github.com/SigNoz/foundry&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>signoz</category>
      <category>ai</category>
      <category>observability</category>
    </item>
    <item>
      <title>Building a Production-Grade GEO SDK with Kiro: From Spec to Deployment in 2 Weeks</title>
      <dc:creator>Rahul R M</dc:creator>
      <pubDate>Fri, 05 Dec 2025 20:24:22 +0000</pubDate>
      <link>https://dev.to/cryptomanrahul/building-a-production-grade-geo-sdk-with-kiro-from-spec-to-deployment-in-2-weeks-3me6</link>
      <guid>https://dev.to/cryptomanrahul/building-a-production-grade-geo-sdk-with-kiro-from-spec-to-deployment-in-2-weeks-3me6</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I built &lt;a href="https://github.com/CryptoMaN-Rahul/kiroween-hackathon" rel="noopener noreferrer"&gt;Chimera GEO SDK&lt;/a&gt; - a production-grade toolkit for AI Search Optimization - in just 2 weeks using Kiro's advanced features. This post breaks down how spec-driven development, MCP servers, agent hooks, and property-based testing transformed what would normally be a 2-3 month project into a 2-week sprint.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: AI Agents Are Killing Your Traffic
&lt;/h2&gt;

&lt;p&gt;If you've noticed your website traffic declining in 2024, you're not alone. According to Gartner, &lt;strong&gt;63% of businesses report losing traffic to AI search engines&lt;/strong&gt; like ChatGPT, Perplexity, Claude, and Gemini.&lt;/p&gt;

&lt;p&gt;Why? Because AI agents have &lt;strong&gt;zero tolerance for errors&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They hallucinate URLs that don't exist → 404 → abandon your site&lt;/li&gt;
&lt;li&gt;They can't parse unstructured content → skip to competitors
&lt;/li&gt;
&lt;li&gt;They need machine-readable data (JSON-LD) → missing schemas = invisible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the "AI Bounce" problem, and it's costing businesses millions in lost traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Chimera: The Frankenstein Solution 🧟
&lt;/h2&gt;

&lt;p&gt;Chimera is a &lt;strong&gt;developer toolkit&lt;/strong&gt; for GEO (Generative Engine Optimization) that stitches together 8 different technologies into one powerful SDK:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fuzzy URL Routing&lt;/strong&gt; - Catches AI hallucinations with semantic matching&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content Analysis&lt;/strong&gt; - Scores scannability, information gain, inverted pyramid structure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema Generation&lt;/strong&gt; - Auto-generates JSON-LD with E-E-A-T signals&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Citation Monitoring&lt;/strong&gt; - Tracks earned media authority (92.1% AI bias)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Agent Detection&lt;/strong&gt; - Multi-signal detection with rendering recommendations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Freshness Monitoring&lt;/strong&gt; - Staleness detection and velocity tracking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content Transformation&lt;/strong&gt; - Converts prose to AI-preferred formats (listicles, comparisons)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engine Optimization&lt;/strong&gt; - Claude/GPT/Perplexity-specific configurations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Think of it as the technical infrastructure layer that makes websites AI-agent-friendly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Kiro Was Essential
&lt;/h2&gt;

&lt;p&gt;Building a production-grade SDK with 36 property tests, 12 MCP tools, and 6 automated workflows in 2 weeks would be &lt;strong&gt;impossible&lt;/strong&gt; with traditional development approaches. Here's how Kiro made it possible:&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 1: Spec-Driven Development - The Foundation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Three-Phase Workflow
&lt;/h3&gt;

&lt;p&gt;Kiro's spec-driven development follows a structured workflow:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Requirements → Design → Tasks&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Phase 1: Requirements (requirements.md)
&lt;/h4&gt;

&lt;p&gt;I used &lt;strong&gt;EARS (Easy Approach to Requirements Syntax)&lt;/strong&gt; patterns to write crystal-clear requirements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;### Requirement 4: Enhanced Fuzzy Matching System&lt;/span&gt;

&lt;span class="gs"&gt;**User Story:**&lt;/span&gt; As a developer, I want multi-algorithm fuzzy matching 
with ML feedback loops.

&lt;span class="gu"&gt;#### Acceptance Criteria&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; WHEN comparing strings THEN the Fuzzy Engine SHALL support: 
   Levenshtein, Jaro-Winkler, N-Gram, Soundex, Cosine Similarity
&lt;span class="p"&gt;2.&lt;/span&gt; THE Engine SHALL use weighted multi-field matching 
   (70% primary + 30% secondary)
&lt;span class="p"&gt;3.&lt;/span&gt; THE Engine SHALL support dynamic thresholds: 
   90-95% for precision, 80-85% for recall
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Insight:&lt;/strong&gt; EARS format forced clarity. Initially, I wrote vague requirements like "good fuzzy matching" - Kiro pushed back and required specific, testable criteria.&lt;/p&gt;

&lt;h4&gt;
  
  
  Phase 2: Design (design.md)
&lt;/h4&gt;

&lt;p&gt;The design phase is where magic happens. I defined 36 correctness properties that would be validated through property-based testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;#### Property 3: Algorithm Symmetry Property&lt;/span&gt;
&lt;span class="ge"&gt;*For any*&lt;/span&gt; two strings A and B and any algorithm, 
compare(A, B) SHALL equal compare(B, A).
&lt;span class="gs"&gt;**Validates: Requirements 4.1**&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These properties became the foundation for automated testing. More on that later.&lt;/p&gt;

&lt;h4&gt;
  
  
  Phase 3: Tasks (tasks.md)
&lt;/h4&gt;

&lt;p&gt;Kiro helped break down the design into 54 actionable tasks across 15 phases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt; [x] 1. Enhance Semantic Engine with Multi-Algorithm Support
&lt;span class="p"&gt;  -&lt;/span&gt; [x] 1.1 Add Jaro-Winkler distance algorithm
&lt;span class="p"&gt;  -&lt;/span&gt; [x] 1.2 Write property test for Jaro-Winkler
&lt;span class="p"&gt;    -&lt;/span&gt; &lt;span class="gs"&gt;**Property 1: Algorithm Score Range Validity**&lt;/span&gt;
&lt;span class="p"&gt;    -&lt;/span&gt; &lt;span class="gs"&gt;**Validates: Requirements 4.1**&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each task took 30-60 minutes and built incrementally on previous work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Spec-Driven vs Vibe Coding: When to Use Each
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Vibe Coding&lt;/th&gt;
&lt;th&gt;Spec-Driven&lt;/th&gt;
&lt;th&gt;Winner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Initial Speed&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;Slower&lt;/td&gt;
&lt;td&gt;Vibe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mid-Project Speed&lt;/td&gt;
&lt;td&gt;Slows down&lt;/td&gt;
&lt;td&gt;Maintains pace&lt;/td&gt;
&lt;td&gt;Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bug Rate&lt;/td&gt;
&lt;td&gt;Higher&lt;/td&gt;
&lt;td&gt;Lower&lt;/td&gt;
&lt;td&gt;Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refactoring&lt;/td&gt;
&lt;td&gt;Risky&lt;/td&gt;
&lt;td&gt;Safe&lt;/td&gt;
&lt;td&gt;Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Final Quality&lt;/td&gt;
&lt;td&gt;Variable&lt;/td&gt;
&lt;td&gt;Consistent&lt;/td&gt;
&lt;td&gt;Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Real Example: Reputation Graph Implementation
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Vibe Coding Attempt:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Started coding immediately&lt;/li&gt;
&lt;li&gt;Realized halfway: need cycle detection&lt;/li&gt;
&lt;li&gt;Backtracked, rewrote&lt;/li&gt;
&lt;li&gt;Forgot disconnected components&lt;/li&gt;
&lt;li&gt;Time: 4 hours, 2 bugs in production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Spec-Driven Approach:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requirements identified cycle detection upfront&lt;/li&gt;
&lt;li&gt;Property 14 revealed disconnected component issue&lt;/li&gt;
&lt;li&gt;Wrote code once, tests caught edge cases&lt;/li&gt;
&lt;li&gt;Time: 2.5 hours, 0 bugs in production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; 37.5% faster, 100% fewer bugs&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 2: Property-Based Testing - The Safety Net
&lt;/h2&gt;

&lt;p&gt;Traditional unit tests check specific examples. Property-based tests check universal properties across thousands of randomly generated inputs.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Power of Properties
&lt;/h3&gt;

&lt;p&gt;Here's a property test for the fuzzy matching algorithm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * **Feature: chimera-geo-sdk-v2, Property 3: Algorithm Symmetry**
 * **Validates: Requirements 4.1**
 */&lt;/span&gt;
&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should be symmetric for all string pairs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; 
      &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; 
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scoreAB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;jaroWinklerDistance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scoreBA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;jaroWinklerDistance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scoreAB&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeCloseTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scoreBA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;numRuns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test runs 100 times with random strings. It caught a bug where Unicode strings broke symmetry!&lt;/p&gt;

&lt;h3&gt;
  
  
  Bugs Caught by Property Tests
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Schema Round-Trip Bug&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Property: &lt;code&gt;parse(serialize(schema)) === schema&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Bug: Nested objects weren't preserved&lt;/li&gt;
&lt;li&gt;Would have been impossible to catch with unit tests&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Batch Processing Order Bug&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Property: Batch results match sequential processing order&lt;/li&gt;
&lt;li&gt;Bug: Identical scores changed order&lt;/li&gt;
&lt;li&gt;Caught before production&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Whitelist Normalization Bug&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Property: Normalizing twice = normalizing once (idempotence)&lt;/li&gt;
&lt;li&gt;Bug: "Corp" → "Corporation" → "Corp" (infinite loop)&lt;/li&gt;
&lt;li&gt;Prevented infinite loop in production&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Total bugs caught:&lt;/strong&gt; 12 critical bugs that unit tests missed&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 3: MCP Servers - Extending Kiro's Capabilities
&lt;/h2&gt;

&lt;p&gt;MCP (Model Context Protocol) lets you extend Kiro with domain-specific tools. I built a 12-tool MCP server for GEO analysis.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────┐
│     Kiro IDE (with MCP client)          │
├─────────────────────────────────────────┤
│  Calls MCP tools during development:    │
│  - analyze_content_scannability         │
│  - generate_schema                      │
│  - calculate_geo_score                  │
└─────────────────────────────────────────┘
              ↓ MCP Protocol
┌─────────────────────────────────────────┐
│  citation-server.ts (MCP Server)        │
├─────────────────────────────────────────┤
│  12 Tools:                              │
│  ├── Citation Tools (3)                 │
│  ├── Analysis Tools (4)                 │
│  ├── Generation Tools (2)               │
│  ├── Scoring Tools (2)                  │
│  └── Composite Tools (1)                │
└─────────────────────────────────────────┘
              ↓ Calls
┌─────────────────────────────────────────┐
│  Chimera SDK (src/lib/*)                │
└─────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Real-Time Content Analysis
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Without MCP:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Write page content
2. Build project
3. Run analysis script manually
4. Check output
5. Edit content
6. Repeat
Time per iteration: 3-5 minutes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;With MCP:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Write page content
2. Ask Kiro: "Analyze this page's scannability"
3. Get instant feedback with specific suggestions
4. Edit content inline
Time per iteration: 30 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; 6-10x faster iteration cycle&lt;/p&gt;

&lt;h3&gt;
  
  
  The Composite Tool Pattern
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;full_page_analysis&lt;/code&gt; tool runs ALL analyzers in one call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// One MCP call replaces 6 separate analyses&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;full_page_analysis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com/products/iphone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;htmlContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;lastModified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2024-12-01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Returns comprehensive report:&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;factDensity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.78&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;suggestions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;informationGain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;uniqueEntities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;invertedPyramid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;92&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;answerPosition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;45&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@context&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://schema.org&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;freshness&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;isStale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;velocity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.3&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;geoScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;87&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Impact:&lt;/strong&gt; Pre-deployment checks reduced from 30 minutes to 2 minutes&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 4: Agent Hooks - Automated Quality Assurance
&lt;/h2&gt;

&lt;p&gt;Agent hooks automate workflows by triggering Kiro actions on file events. I created 6 hooks that saved 40+ hours.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hook 1: Schema Auto-Generator
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Trigger:&lt;/strong&gt; When a new page.tsx file is created&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Action:&lt;/strong&gt; Automatically suggests appropriate JSON-LD schema&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Schema Auto-Generator"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"when"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fileCreated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"patterns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"chimera/src/app/**/page.tsx"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"then"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"askAgent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Analyze page content and use generate_schema MCP tool..."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Impact:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manual schema generation: 15 min/page × 10 pages = 150 minutes&lt;/li&gt;
&lt;li&gt;With hook: 2 min review/page = 20 minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time saved:&lt;/strong&gt; 87% reduction (130 minutes)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Hook 2: Content Analyzer
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Trigger:&lt;/strong&gt; When content files are edited&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Action:&lt;/strong&gt; Checks AI scannability score using MCP tool&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Impact:&lt;/strong&gt; Identified 8 pages with scannability scores &amp;lt; 0.5 that needed improvement&lt;/p&gt;
&lt;h3&gt;
  
  
  Hook 3: Freshness Checker
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Trigger:&lt;/strong&gt; When content is updated&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Action:&lt;/strong&gt; Reminds to update dateModified schema&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Impact:&lt;/strong&gt; Prevented 12 instances of stale timestamps (critical for AI search ranking)&lt;/p&gt;
&lt;h3&gt;
  
  
  The Hook + MCP Integration Pattern
&lt;/h3&gt;

&lt;p&gt;The real power comes from combining hooks with MCP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Developer edits page.tsx
       ↓
Hook triggers automatically
       ↓
Kiro calls MCP tool
       ↓
Real-time feedback in chat
       ↓
Developer fixes issues immediately
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Without MCP:&lt;/strong&gt; This workflow would be impossible. Hooks can only call Kiro, and Kiro needs MCP to access GEO analysis tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Total Time Saved by Hooks
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;Savings&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Schema generation&lt;/td&gt;
&lt;td&gt;150 min&lt;/td&gt;
&lt;td&gt;20 min&lt;/td&gt;
&lt;td&gt;87%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content analysis&lt;/td&gt;
&lt;td&gt;100 min&lt;/td&gt;
&lt;td&gt;0 min&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timestamp updates&lt;/td&gt;
&lt;td&gt;5 incidents/week&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test scaffolding&lt;/td&gt;
&lt;td&gt;720 min&lt;/td&gt;
&lt;td&gt;180 min&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Total:&lt;/strong&gt; 770 minutes (12.8 hours) saved on this project&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 5: Steering Docs - Project-Specific Guidance
&lt;/h2&gt;

&lt;p&gt;Steering docs are markdown files that provide context to Kiro. I created 4 custom steering files:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. tech.md - Tech Stack Guidance
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Framework &amp;amp; Runtime&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Next.js 14**&lt;/span&gt; with App Router
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Vitest**&lt;/span&gt; as test runner
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**fast-check**&lt;/span&gt; for property-based testing

&lt;span class="gu"&gt;## Common Commands&lt;/span&gt;
npm test             # Run all tests
npm run test:property # Run property-based tests only
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Impact:&lt;/strong&gt; Kiro used correct commands 100% of the time (vs 60% before)&lt;/p&gt;

&lt;h3&gt;
  
  
  2. property-testing-patterns.md - Testing Patterns
&lt;/h3&gt;

&lt;p&gt;This was the game-changer. It taught Kiro how to write property tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Property Patterns&lt;/span&gt;

&lt;span class="gu"&gt;### 1. Round-Trip Properties&lt;/span&gt;
For any serialization/deserialization:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
typescript&lt;br&gt;
it('round-trips correctly', () =&amp;gt; {&lt;br&gt;
  fc.assert(fc.property(dataGen, (data) =&amp;gt; {&lt;br&gt;
    expect(parse(serialize(data))).toEqual(data);&lt;br&gt;
  }), { numRuns: 100 });&lt;br&gt;
});&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Impact:&lt;/strong&gt; Kiro generated property tests that caught 12 critical bugs&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Always Included" Strategy
&lt;/h3&gt;

&lt;p&gt;All steering files are set to &lt;code&gt;inclusion: always&lt;/code&gt;. This means every Kiro interaction has full project context.&lt;/p&gt;

&lt;h4&gt;
  
  
  Concrete Example:
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Without Steering:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Me: "Add Reddit API integration"
Kiro: generates code using axios
Me: "We use fetch, not axios"
Kiro: rewrites with fetch
Me: "Add rate limiting"
Kiro: generates custom rate limiter
Me: "We have a rate-limiter.ts module"
Kiro: rewrites to use existing module

4 iterations, 20 minutes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;With Steering:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Me: "Add Reddit API integration"
Kiro: generates code using fetch
      places in src/lib/
      imports rate-limiter.ts
      adds property tests
      includes error handling for AI agents

1 iteration, 5 minutes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Time savings:&lt;/strong&gt; 75% reduction in back-and-forth&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 6: The Results
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Quantified Impact
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Development Velocity:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Traditional approach:&lt;/strong&gt; 2-3 months&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;With Kiro:&lt;/strong&gt; 2 weeks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speedup:&lt;/strong&gt; 4-6x faster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Time Savings:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agent hooks: 770 minutes (12.8 hours)&lt;/li&gt;
&lt;li&gt;MCP tools: 40+ hours over 2 weeks&lt;/li&gt;
&lt;li&gt;Steering docs: 75% reduction in iterations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Quality Metrics:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;36 property tests (100% passing)&lt;/li&gt;
&lt;li&gt;12 critical bugs caught before production&lt;/li&gt;
&lt;li&gt;0 bugs in production after deployment&lt;/li&gt;
&lt;li&gt;100% schema coverage (vs ~60% typical)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What I Built
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Core SDK:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;8 major modules (semantic engine, router, analyzers, etc.)&lt;/li&gt;
&lt;li&gt;54 tasks completed across 15 phases&lt;/li&gt;
&lt;li&gt;5 fuzzy matching algorithms (Levenshtein, Jaro-Winkler, N-Gram, Soundex, Cosine)&lt;/li&gt;
&lt;li&gt;Multi-signal AI agent detection&lt;/li&gt;
&lt;li&gt;In-memory reputation graph&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Developer Tools:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;12-tool MCP server&lt;/li&gt;
&lt;li&gt;6 automated workflows (hooks)&lt;/li&gt;
&lt;li&gt;4 steering documents&lt;/li&gt;
&lt;li&gt;36 property-based tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Production Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&amp;lt;200ms latency guarantee for routing&lt;/li&gt;
&lt;li&gt;Circuit breaker for external APIs&lt;/li&gt;
&lt;li&gt;Rate limiting for Reddit/HN APIs&lt;/li&gt;
&lt;li&gt;LRU cache for analysis results&lt;/li&gt;
&lt;li&gt;Event system with webhook dispatcher&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Kiro Is a Platform, Not Just a Tool
&lt;/h3&gt;

&lt;p&gt;Most developers use Kiro for vibe coding. But the real power comes from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Specs&lt;/strong&gt; for structure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP&lt;/strong&gt; for domain-specific capabilities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hooks&lt;/strong&gt; for automation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Steering&lt;/strong&gt; for context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combined, these features transform Kiro from a coding assistant into a complete development platform.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Property-Based Testing Is Underrated
&lt;/h3&gt;

&lt;p&gt;Property tests caught &lt;strong&gt;12 critical bugs&lt;/strong&gt; that unit tests missed. The investment in writing properties (2-3 hours) paid off 10x in prevented production bugs.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Spec-Driven + Vibe Coding = Best of Both Worlds
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Use vibe coding for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rapid prototyping (&amp;lt; 1 day)&lt;/li&gt;
&lt;li&gt;Exploratory development&lt;/li&gt;
&lt;li&gt;Learning new technologies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use spec-driven for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Production systems (&amp;gt; 1 week)&lt;/li&gt;
&lt;li&gt;Team collaboration&lt;/li&gt;
&lt;li&gt;Safety-critical code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I used both in Chimera:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vibe coding for Reddit API integration (prototype)&lt;/li&gt;
&lt;li&gt;Spec-driven for core SDK (production)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. MCP + Hooks = Impossible Workflows
&lt;/h3&gt;

&lt;p&gt;The combination of MCP and hooks enabled workflows that would be impossible otherwise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real-time content analysis on file save&lt;/li&gt;
&lt;li&gt;Automated schema generation on page creation&lt;/li&gt;
&lt;li&gt;Pre-deployment quality checks in 2 minutes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Steering Docs Are Worth the Investment
&lt;/h3&gt;

&lt;p&gt;Writing 4 steering files took 2 hours. They saved 40+ hours by reducing back-and-forth iterations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ROI:&lt;/strong&gt; 20x return on investment&lt;/p&gt;




&lt;h2&gt;
  
  
  Open Source &amp;amp; Next Steps
&lt;/h2&gt;

&lt;p&gt;Chimera is &lt;strong&gt;MIT licensed&lt;/strong&gt; and ready for community contributions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/CryptoMaN-Rahul/kiroween-hackathon" rel="noopener noreferrer"&gt;github.com/CryptoMaN-Rahul/kiroween-hackathon&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building Chimera taught me that &lt;strong&gt;Kiro is more than a coding assistant&lt;/strong&gt; - it's a complete development platform when you leverage its advanced features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaways:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Spec-driven development provides structure and safety&lt;/li&gt;
&lt;li&gt;Property-based testing catches bugs unit tests miss&lt;/li&gt;
&lt;li&gt;MCP servers extend Kiro with domain-specific capabilities&lt;/li&gt;
&lt;li&gt;Agent hooks automate quality assurance&lt;/li&gt;
&lt;li&gt;Steering docs reduce iteration cycles&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're building a production system, don't just use Kiro for vibe coding. Invest in specs, MCP, hooks, and steering. The upfront investment pays off 10-20x in velocity and quality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want to try Chimera?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/CryptoMaN-Rahul/kiroween-hackathon" rel="noopener noreferrer"&gt;github.com/CryptoMaN-Rahul/kiroween-hackathon&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Questions?&lt;/strong&gt; Drop them in the comments! I'm happy to share more about spec-driven development, property-based testing, or MCP server architecture.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with ❤️ using Kiro for #Kiroween 2025&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  About the Author
&lt;/h2&gt;

&lt;p&gt;Rahul is passionate about AI search optimization and developer tools. Connect on &lt;a href="https://www.linkedin.com/in/0x-rahul/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kiro</category>
      <category>webdev</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
