<?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: Heet Ranpura</title>
    <description>The latest articles on DEV Community by Heet Ranpura (@heet_ranpura).</description>
    <link>https://dev.to/heet_ranpura</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%2F4182320%2Feed290e3-321d-47ae-99b5-263e99e66a75.png</url>
      <title>DEV Community: Heet Ranpura</title>
      <link>https://dev.to/heet_ranpura</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/heet_ranpura"/>
    <language>en</language>
    <item>
      <title>I stopped asking AI to count. Now it catches 95.6% of the mistakes in crochet patterns, on a laptop</title>
      <dc:creator>Heet Ranpura</dc:creator>
      <pubDate>Sun, 18 Oct 2026 16:14:25 +0000</pubDate>
      <link>https://dev.to/heet_ranpura/i-stopped-asking-ai-to-count-now-it-catches-956-of-the-mistakes-in-crochet-patterns-on-a-laptop-1el0</link>
      <guid>https://dev.to/heet_ranpura/i-stopped-asking-ai-to-count-now-it-catches-956-of-the-mistakes-in-crochet-patterns-on-a-laptop-1el0</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Round 5 says 31 stitches. You've counted three times. You keep getting 30.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Somewhere tonight, someone is pulling out an evening's work because of a number like that. They will blame their own hands. Sometimes they're right. Sometimes the pattern itself is wrong, and there's no way to tell which.&lt;/p&gt;

&lt;p&gt;So I gave the job to an AI. I showed an open-weight model 60 crochet patterns with 45 wrong rows hidden in them and asked it to find the mistakes. It found 20. Along the way it flagged 78 rows that were perfectly fine. Then I tried a model 30 times bigger on some of the same patterns. It still missed almost half.&lt;/p&gt;

&lt;p&gt;The problem wasn't crochet. The problem was that I was asking a language model to do arithmetic.&lt;/p&gt;

&lt;p&gt;So I stopped. In StitchProof, the model only &lt;strong&gt;reads&lt;/strong&gt; the pattern and turns each row into a tiny program. Plain Python does all the &lt;strong&gt;counting&lt;/strong&gt;. With that split, and a 4B model fine-tuned on Tinker to do the reading, StitchProof caught &lt;strong&gt;95.6% (43/45)&lt;/strong&gt; of the same mistakes, runs on a laptop with the Wi-Fi off, and shows the arithmetic behind every flag:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Rnd 5 says (31), but '(sc 3, inc) x6' uses 24 of 24 stitches and makes 30.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This post is about crochet. The idea in it is for anyone who has watched an LLM confidently get a number wrong: &lt;strong&gt;let the model translate, and let code count.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhwgwnkvl7zhc7mlk84is.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhwgwnkvl7zhc7mlk84is.gif" alt="StitchProof reading a pattern row by row and flagging two rows that don't add up" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;StitchProof is a private tech editor for crochet patterns.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Crochet is bigger than it looks. Ravelry, the main home for knitters and crocheters, had &lt;a href="https://en.wikipedia.org/wiki/Ravelry" rel="noopener noreferrer"&gt;almost 9 million registered users&lt;/a&gt; by 2020, and independent designers sell their patterns there and on Etsy as PDFs. Before a pattern goes on sale, many designers pay a human &lt;a href="https://fiverr.com/mevlinngusickgo/tech-edit-your-crochet-pattern" rel="noopener noreferrer"&gt;tech editor&lt;/a&gt; to check every stitch count, because one wrong number in round 5 means every maker who buys it tears out their work.&lt;/p&gt;

&lt;p&gt;StitchProof does the stitch-math part of that job:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Who&lt;/th&gt;
&lt;th&gt;What they do&lt;/th&gt;
&lt;th&gt;What they get&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Designers&lt;/strong&gt; (would pay)&lt;/td&gt;
&lt;td&gt;Paste a draft, open a &lt;code&gt;.txt&lt;/code&gt;, &lt;code&gt;.md&lt;/code&gt; or &lt;code&gt;.pdf&lt;/code&gt;, or photograph a printed page&lt;/td&gt;
&lt;td&gt;Every row marked OK, ERROR, WARNING or CAN'T CHECK, the exact numbers behind each flag, a silhouette chart of the piece and a downloadable report&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Makers&lt;/strong&gt; (free)&lt;/td&gt;
&lt;td&gt;Type the stitches they had and paste the row they're stuck on&lt;/td&gt;
&lt;td&gt;&lt;em&gt;"The pattern says 31. These instructions make 30 from your 24 stitches. The pattern itself doesn't add up here, so it's not you."&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The side hustle:&lt;/strong&gt; makers use it free, which is how designers hear about it. Designers pay per pattern check, or a small monthly fee if they publish often. A per-check price only works if each check costs nothing to run, which is exactly what a small local model gives me. (That's my business hypothesis, not a claim about revenue.)&lt;/p&gt;




&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/bOjLyfRDq1Y" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The waits for the model and for Gemma are sped up 3x in the video. Everything you see runs on one MacBook.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Every screen, one at a time
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Start.&lt;/strong&gt; Paste a pattern, open a file, or photograph a printed page.&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%2F5t10s2l693lgrcqnjgcq.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%2F5t10s2l693lgrcqnjgcq.png" alt="The StitchProof start screen with three ways to bring in a pattern" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Check a pattern.&lt;/strong&gt; The sample has two deliberate mistakes. Rows are annotated in place as the model reads them, like a spellchecker. Select a row and the panel shows the stitches available, used, made and stated.&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%2Fbugvx4yagqpz09jv6pj5.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%2Fbugvx4yagqpz09jv6pj5.png" alt="Rnd 5 flagged as an error: it says 31 but makes 30" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The second mistake.&lt;/strong&gt; Rnd 11 repeats &lt;code&gt;(sc 2, dec)&lt;/code&gt; 5 times instead of 6, so it makes 15, not 18, and leaves 4 stitches unworked. StitchProof reports both problems.&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%2F6fr7mdlkx2xlbzmscym5.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%2F6fr7mdlkx2xlbzmscym5.png" alt="Rnd 11 flagged: count mismatch and leftover stitches" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;silhouette&lt;/strong&gt; plots stitches per round. For amigurumi it traces the outline of the finished piece, so a mistake shows up as a spike or a dent, hatched and labelled.&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%2Frg8bvkhvf7t78fmiovgw.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%2Frg8bvkhvf7t78fmiovgw.png" alt="The silhouette chart, with the two error rows hatched" width="800" height="485"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Fix and check again.&lt;/strong&gt; Edit Rnd 5 and re-run. Unchanged rows come straight from a local cache, so only the edited row goes back to the model.&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%2Fwkos3mhe7bz124nlghq7.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%2Fwkos3mhe7bz124nlghq7.png" alt="Editing the pattern text" width="800" height="500"&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%2F0f5wut34zsshnjpkwhm1.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%2F0f5wut34zsshnjpkwhm1.png" alt="After the fix, one error left: Rnd 11" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Light or dark.&lt;/strong&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%2Fwcndhg7p8ct2k8tm8p1k.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%2Fwcndhg7p8ct2k8tm8p1k.png" alt="StitchProof in dark mode" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Printed patterns.&lt;/strong&gt; Plenty of patterns live in books and on paper. Drop in a photo and Gemma 4 transcribes it on your computer.&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%2Fn0bye7p3jrze62e6ut9d.jpg" 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%2Fn0bye7p3jrze62e6ut9d.jpg" alt="A phone-style photo of a printed pattern" width="800" height="265"&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%2F6ht4dmluwd4p47czwpy5.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%2F6ht4dmluwd4p47czwpy5.png" alt="Gemma 4 reading the photo locally" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Review before checking.&lt;/strong&gt; The photo sits beside the editable transcription, so you can fix any misread before a single stitch is counted.&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%2F3awih3mx9sdo2eehzb48.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%2F3awih3mx9sdo2eehzb48.png" alt="The photo beside Gemma's editable transcription" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. Stuck on a row.&lt;/strong&gt; The maker mode: one row, one plain answer.&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%2F4312hc2g2z57nn2fmwxw.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%2F4312hc2g2z57nn2fmwxw.png" alt="Stuck on a row: the pattern says 31, these instructions make 30, so it's not you" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HeetRanpura" rel="noopener noreferrer"&gt;
        HeetRanpura
      &lt;/a&gt; / &lt;a href="https://github.com/HeetRanpura/stitchproof" rel="noopener noreferrer"&gt;
        stitchproof
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A local tech editor for crochet patterns: a fine-tuned 4B model reads each row, plain code checks every stitch count.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;StitchProof&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;A local tech editor for crochet patterns.&lt;/strong&gt;
A small fine-tuned model reads every row. Plain code checks every stitch count. Nothing leaves your computer.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/HeetRanpura/stitchproof/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/HeetRanpura/stitchproof/actions/workflows/ci.yml/badge.svg" alt="CI"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/0bdd5e895e994c35aba94f89fda45f3ffa92ebc08eb0b556d0c1d52f645769d9/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d613834373166"&gt;&lt;img src="https://camo.githubusercontent.com/0bdd5e895e994c35aba94f89fda45f3ffa92ebc08eb0b556d0c1d52f645769d9/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d613834373166" alt="License: MIT"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/47ed34d5238ce7a20b3159e600cf1cbfca9aa58d8bf332b687a900d4afb62950/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f707974686f6e2d332e31312d326132343230"&gt;&lt;img src="https://camo.githubusercontent.com/47ed34d5238ce7a20b3159e600cf1cbfca9aa58d8bf332b687a900d4afb62950/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f707974686f6e2d332e31312d326132343230" alt="Python 3.11"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/d54a303d80c3417226b6c330288059b37fa369b899aa498069a2ec957379da85/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f72756e732d3130302532352532306c6f63616c2d346636623463"&gt;&lt;img src="https://camo.githubusercontent.com/d54a303d80c3417226b6c330288059b37fa369b899aa498069a2ec957379da85/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f72756e732d3130302532352532306c6f63616c2d346636623463" alt="Runs locally"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HeetRanpura/stitchproof/docs/screenshots/app_review.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHeetRanpura%2Fstitchproof%2FHEAD%2Fdocs%2Fscreenshots%2Fapp_review.png" alt="StitchProof checking a pattern: every row annotated in place, two errors flagged with their numbers"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/HeetRanpura/stitchproof/docs/demo/stitchproof_demo.mp4" rel="noopener noreferrer"&gt;Demo video&lt;/a&gt; · &lt;a href="https://github.com/HeetRanpura/stitchproof#results" rel="noopener noreferrer"&gt;Results&lt;/a&gt; · &lt;a href="https://github.com/HeetRanpura/stitchproof#how-it-works" rel="noopener noreferrer"&gt;How it works&lt;/a&gt; · &lt;a href="https://github.com/HeetRanpura/stitchproof#architecture" rel="noopener noreferrer"&gt;Architecture&lt;/a&gt; · &lt;a href="https://github.com/HeetRanpura/stitchproof#run-it-locally" rel="noopener noreferrer"&gt;Run it&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Round 5 says 31. You count 30.&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;You've unravelled it twice. Is the pattern wrong, or are you?&lt;/p&gt;
&lt;p&gt;Crochet designers sell their patterns as PDFs on Etsy and Ravelry. Before publishing, many pay a human tech editor to check every single stitch count, because one wrong number in round 5 means hundreds of makers tearing out an evening's work. Errata are a fact of life.&lt;/p&gt;
&lt;p&gt;So why not ask an AI? We tried. Given 60 test patterns and asked directly to find the rows whose counts are wrong, the open-weight Qwen3.5-4B caught &lt;strong&gt;44.4% (20/45)&lt;/strong&gt; of the mistakes. Chatbots are famously bad at crochet. They "count" stitches they never counted.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;StitchProof never asks&lt;/strong&gt;…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HeetRanpura/stitchproof" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;MIT licensed. 149 Python tests, plus frontend tests, run in CI on every push. Every result in this post comes from files in &lt;a href="https://github.com/HeetRanpura/stitchproof/tree/main/eval/results" rel="noopener noreferrer"&gt;&lt;code&gt;eval/results/&lt;/code&gt;&lt;/a&gt;, and every rate is shown with its count.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Built on open source:&lt;/strong&gt; Qwen3.5 (Qwen team), Gemma 4 (Google DeepMind), Tinker and &lt;code&gt;tinker-cookbook&lt;/code&gt; (Thinking Machines), llama.cpp, Ollama, FastAPI and React. Full credits are in the &lt;a href="https://github.com/HeetRanpura/stitchproof#credits-and-license" rel="noopener noreferrer"&gt;README&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The one rule: the model never counts
&lt;/h3&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%2Fxvpvq05s9esjzra2ta1b.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%2Fxvpvq05s9esjzra2ta1b.png" alt="The model translates a row into a stitch program, and the simulator does the counting" width="800" height="715"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Segment.&lt;/strong&gt; The pattern is split into lines. Each line goes to the model with a short header (title, US terms, rounds or rows) and the line before it, for context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Translate.&lt;/strong&gt; A fine-tuned Qwen3.5-4B turns one line into one &lt;code&gt;LineProgram&lt;/code&gt; JSON object: work, increase, decrease, skip, "to end", turning chain, repeat, plus the row label and the stated count, &lt;strong&gt;copied exactly as written, even when it's wrong&lt;/strong&gt;. That last part is the whole trick: the model has to faithfully transcribe the designer's mistake so the code can catch it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simulate.&lt;/strong&gt; A deterministic engine walks the rows in order: magic rings, foundation chains and skipped chains, turning chains that do or don't count, repeats that must divide "around", ranges like &lt;code&gt;Rnds 6-9&lt;/code&gt;. It reports &lt;code&gt;COUNT_MISMATCH&lt;/code&gt;, &lt;code&gt;OVERFLOW&lt;/code&gt;, &lt;code&gt;LEFTOVER&lt;/code&gt;, &lt;code&gt;REPEAT_REMAINDER&lt;/code&gt;, &lt;code&gt;NO_STATED_COUNT&lt;/code&gt; or &lt;code&gt;CANT_CHECK&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explain.&lt;/strong&gt; Every verdict is a fixed template filled with numbers. Nothing the user reads about a count is generated by a model.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The model's instructions say it plainly (&lt;a href="https://github.com/HeetRanpura/stitchproof/blob/main/src/stitchproof/prompts.py" rel="noopener noreferrer"&gt;&lt;code&gt;prompts.py&lt;/code&gt;&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Transcribe exactly. Never fix or compute counts. stated_count = the count
  written ("(18)", "[18 sts]", "- 18 st", "you should have 18"), even if wrong.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this is what "counting" looks like on the other side, straight from &lt;a href="https://github.com/HeetRanpura/stitchproof/blob/main/src/stitchproof/simulate.py" rel="noopener noreferrer"&gt;&lt;code&gt;simulate.py&lt;/code&gt;&lt;/a&gt;. No model anywhere near it:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;op&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Work&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;          &lt;span class="c1"&gt;# "sc 3": 1 stitch into each of the next 3
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;made&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntoOne&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;     &lt;span class="c1"&gt;# "inc": 2 stitches into 1
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;made&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Together&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;    &lt;span class="c1"&gt;# "dec": 2 stitches worked together into 1
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;made&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the code can't be sure (lace, multi-size patterns, UK terms, short rows, a line the model couldn't read), the row says &lt;strong&gt;CAN'T CHECK&lt;/strong&gt; with a reason. It never guesses. After a wrong row, the engine re-syncs to the designer's stated count, so one mistake is reported once instead of cascading down the whole pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture at runtime
&lt;/h3&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%2F2llzcpwdeeb7653zzyhw.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%2F2llzcpwdeeb7653zzyhw.png" alt="Everything runs on the laptop: browser, FastAPI, llama.cpp with the fine-tuned model, and Ollama with Gemma 4" width="800" height="592"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Streaming:&lt;/strong&gt; results arrive row by row over Server-Sent Events, so the sheet fills in as the model reads. That's exact, not a trick: each row's count depends only on the rows before it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-line cache:&lt;/strong&gt; each parsed line is cached by a hash of (header, previous line, line), so re-checking an edited pattern only sends the changed rows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private by construction:&lt;/strong&gt; the server binds to &lt;code&gt;127.0.0.1&lt;/code&gt;, fonts and icons are bundled, there's no telemetry, and uploaded photos are read from a temp file and deleted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stack:&lt;/strong&gt; FastAPI + Uvicorn, React 19 + Vite + Tailwind, llama.cpp (&lt;code&gt;llama-server&lt;/code&gt;, Metal), Ollama for Gemma 4, pypdf, Jinja2 and Matplotlib for reports.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Building the model on Tinker
&lt;/h3&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%2Fc1wm1q0lafgfsmxhhdgj.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%2Fc1wm1q0lafgfsmxhhdgj.png" alt="The training pipeline: generate shapes, seed mistakes, render notation, rewrite with teacher models, fine-tune, evaluate, export" width="800" height="625"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No published patterns were used anywhere.&lt;/strong&gt; Real patterns are someone's copyrighted work, so every training example is generated by my own code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;shapes.py&lt;/code&gt; builds pattern structures: spheres, tubes, cones, flat pieces, triangles, mixed stitches.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;errors.py&lt;/code&gt; seeds known mistakes (wrong stated count, wrong repeat count, wrong run length, wrong foundation chain, a repeat that doesn't divide "around"). Every seeded pattern is checked: the simulator must flag exactly the seeded rows.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;render.py&lt;/code&gt; writes the notation in many styles. &lt;strong&gt;Four styles and the whole cone shape are held out of training&lt;/strong&gt; and used only for evaluation.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;paraphrase.py&lt;/code&gt; asks two teacher models on Tinker, &lt;code&gt;gpt-oss-120b&lt;/code&gt; and &lt;code&gt;Qwen3.5-397B-A17B&lt;/code&gt;, to rewrite some patterns in a natural designer's voice. Code then checks that every number and row label survived the rewrite.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ood.py&lt;/code&gt; asks a third family, &lt;strong&gt;Kimi-K2.6&lt;/strong&gt;, to write 25 patterns from structure alone, with none of my templates. That's the test for "did it learn crochet, or just my generator?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Training was a LoRA on &lt;code&gt;Qwen/Qwen3.5-4B&lt;/code&gt; with &lt;code&gt;tinker-cookbook&lt;/code&gt;'s supervised loop: rank 32, one epoch, 1,410 line examples, thinking disabled. Validation loss went from 0.303 to 0.0028.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The whole build cost US$1.62 of Tinker credit&lt;/strong&gt; (&lt;a href="https://github.com/HeetRanpura/stitchproof/blob/main/eval/results/costs.md" rel="noopener noreferrer"&gt;costs.md&lt;/a&gt;):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model on Tinker&lt;/th&gt;
&lt;th&gt;Used for&lt;/th&gt;
&lt;th&gt;US$&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5-4B&lt;/td&gt;
&lt;td&gt;LoRA training (1 epoch + validation)&lt;/td&gt;
&lt;td&gt;1.018&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5-4B&lt;/td&gt;
&lt;td&gt;sampling for baselines, the fine-tuned eval, the export check&lt;/td&gt;
&lt;td&gt;0.359&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kimi-K2.6&lt;/td&gt;
&lt;td&gt;writing the out-of-generator test set&lt;/td&gt;
&lt;td&gt;0.099&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.5-397B-A17B&lt;/td&gt;
&lt;td&gt;training-data rewrites&lt;/td&gt;
&lt;td&gt;0.084&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gpt-oss-120b&lt;/td&gt;
&lt;td&gt;training-data rewrites + the large-model baseline&lt;/td&gt;
&lt;td&gt;0.041&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;storage&lt;/td&gt;
&lt;td&gt;checkpoints&lt;/td&gt;
&lt;td&gt;0.017&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.619&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Results
&lt;/h3&gt;

&lt;p&gt;Four approaches, the same eval sets, the same prompt and the same parser, at temperature 0:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A:&lt;/strong&gt; base Qwen3.5-4B, asked directly to find the rows with wrong counts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;B:&lt;/strong&gt; gpt-oss-120b asked the same way (a much bigger model, run on 15 patterns as a reference)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;C:&lt;/strong&gt; base Qwen3.5-4B reading each line + the simulator&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;D:&lt;/strong&gt; &lt;strong&gt;StitchProof:&lt;/strong&gt; fine-tuned Qwen3.5-4B reading each line + the simulator&lt;/li&gt;
&lt;/ul&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%2Fgeb4f2pe4346gii2zwaq.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%2Fgeb4f2pe4346gii2zwaq.png" alt="Results: errors caught and false alarms for approaches A, B, C and D on both test sets" width="800" height="643"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;A: base, asked directly&lt;/th&gt;
&lt;th&gt;C: base + simulator&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;D: StitchProof&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Held-out: errors caught&lt;/td&gt;
&lt;td&gt;44.4% (20/45)&lt;/td&gt;
&lt;td&gt;75.6% (34/45)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;95.6% (43/45)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Held-out: false alarms&lt;/td&gt;
&lt;td&gt;13.7% (78/571)&lt;/td&gt;
&lt;td&gt;42.6% (243/571)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;22.2% (127/571)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Other model family: errors caught&lt;/td&gt;
&lt;td&gt;61.1% (11/18)&lt;/td&gt;
&lt;td&gt;100.0% (18/18)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100.0% (18/18)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Other model family: false alarms&lt;/td&gt;
&lt;td&gt;11.2% (23/205)&lt;/td&gt;
&lt;td&gt;50.7% (104/205)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8.3% (17/205)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Other model family: whole pattern right&lt;/td&gt;
&lt;td&gt;32.0% (8/25)&lt;/td&gt;
&lt;td&gt;16.0% (4/25)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;80.0% (20/25)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Valid JSON (held-out)&lt;/td&gt;
&lt;td&gt;86.7% (52/60)&lt;/td&gt;
&lt;td&gt;78.0% (663/850)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100.0% (850/850)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stated count copied faithfully (held-out)&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;67.4% (415/616)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100.0% (616/616)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What the table says, in plain words:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Asking a model to count doesn't work, at any size.&lt;/strong&gt; A misses more than half the mistakes. B, roughly 30 times bigger, caught 54.5% (6/11) with 38.3% (62/162) false alarms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The simulator alone isn't enough either.&lt;/strong&gt; C catches more, but raises false alarms on 42.6% of clean rows, because the base model can't reliably write the program format. Only 78.0% of its outputs are even valid JSON.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fine-tuning the reader is what makes it work.&lt;/strong&gt; D writes valid JSON every time, copies every stated count faithfully, and on patterns written by a model family it never saw, gets the &lt;strong&gt;whole pattern right 80% of the time&lt;/strong&gt; with 8.3% false alarms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's cheaper to run, too.&lt;/strong&gt; On the held-out set, D needed about 40% fewer output tokens than C (32,366 vs 54,442) for the same rows, because it writes the program cleanly the first time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where it still fails (measured, not hidden)
&lt;/h3&gt;

&lt;p&gt;Almost all of D's false alarms on the held-out set come from &lt;strong&gt;one notation it never saw&lt;/strong&gt;: &lt;code&gt;*sc, inc; rep from * 4 more times&lt;/code&gt;, which means 5 repeats in total. The model writes 4. On rows in that style it raises false alarms on 97.5% (115/118) of clean rows, and it's also why D catches only 66.7% (4/6) of the wrong-repeat-count mistakes. On rows in the other three unseen styles, D parses 99.8% (466/467) exactly, with 2.7% (12/438) false alarms.&lt;/p&gt;

&lt;p&gt;I didn't retrain on that style, because that would be tuning on the test set. It goes into the next version's training templates, with fresh held-out styles to test against.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting the model out of the cloud
&lt;/h3&gt;

&lt;p&gt;Patterns are a designer's unreleased work, so the fine-tuned model had to run locally. I exported the LoRA with Tinker's weights API, converted it for llama.cpp and run it on a 4-bit base:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Disk&lt;/th&gt;
&lt;th&gt;RAM&lt;/th&gt;
&lt;th&gt;Load time&lt;/th&gt;
&lt;th&gt;Per row (M4 MacBook)&lt;/th&gt;
&lt;th&gt;Agreement with Tinker&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2.93 GB&lt;/td&gt;
&lt;td&gt;3.18 GB&lt;/td&gt;
&lt;td&gt;2.5 s&lt;/td&gt;
&lt;td&gt;2.36 s mean&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100.0% (50/50)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One gotcha worth sharing with anyone fine-tuning Qwen3.5 on Tinker:&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%2Fu7tp5z9l0toef6oek1g3.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%2Fu7tp5z9l0toef6oek1g3.png" alt="Tinker trains separate Q, K and V LoRAs; llama.cpp needs one fused LoRA, built by stacking A and block-diagonalising B" width="800" height="583"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tinker trains the linear-attention Q, K and V projections as &lt;strong&gt;separate&lt;/strong&gt; LoRAs, but Qwen3.5 stores them as &lt;strong&gt;one fused&lt;/strong&gt; &lt;code&gt;in_proj_qkv&lt;/code&gt; weight, so llama.cpp can't load the adapter as-is. An exact fused LoRA stacks the A matrices and puts the B matrices on a block diagonal (&lt;a href="https://github.com/HeetRanpura/stitchproof/blob/main/src/stitchproof/train/export.py" rel="noopener noreferrer"&gt;&lt;code&gt;export.py&lt;/code&gt;&lt;/a&gt;):&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="n"&gt;dim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;            &lt;span class="c1"&gt;# rank r -&amp;gt; 3r
&lt;/span&gt;    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;block_diag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;       &lt;span class="c1"&gt;# llama.cpp scales by alpha / rank
&lt;/span&gt;    &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.linear_attn.in_proj_qkv.lora_A.weight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contiguous&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.linear_attn.in_proj_qkv.lora_B.weight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contiguous&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;* 3.0&lt;/code&gt; matters: the fused LoRA has rank 3r, and llama.cpp divides by rank, so without it every update would be a third of what Tinker trained. The equivalence check (50 validation lines, same prompt and parser, local vs Tinker) is what proved the conversion exact.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading paper patterns with Gemma 4
&lt;/h3&gt;

&lt;p&gt;Gemma 4 (&lt;code&gt;gemma4:e4b-it-qat&lt;/code&gt; in Ollama) has one job: turn a photo of a printed pattern into text, locally. It never counts either. On 12 held-out patterns rendered as a clean scan and as a phone-style photo (slight rotation, uneven light, noise, JPEG):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Typed text&lt;/th&gt;
&lt;th&gt;Scan → Gemma&lt;/th&gt;
&lt;th&gt;Photo → Gemma&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Numbers kept&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;98.9% (357/361)&lt;/td&gt;
&lt;td&gt;98.1% (354/361)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Errors caught, end to end&lt;/td&gt;
&lt;td&gt;100.0% (12/12)&lt;/td&gt;
&lt;td&gt;91.7% (11/12)&lt;/td&gt;
&lt;td&gt;100.0% (12/12)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;False alarms, end to end&lt;/td&gt;
&lt;td&gt;23.2% (22/95)&lt;/td&gt;
&lt;td&gt;25.3% (24/95)&lt;/td&gt;
&lt;td&gt;22.1% (21/95)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lines character-perfect&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;50.3% (81/161)&lt;/td&gt;
&lt;td&gt;42.2% (68/161)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Gemma keeps the numbers but likes to drop separators like " - ". That's why the app always puts the photo next to the editable transcription before anything is checked.&lt;/p&gt;

&lt;h3&gt;
  
  
  How the build itself ran
&lt;/h3&gt;

&lt;p&gt;I wrote a spec with milestones and checkpoints, then built StitchProof with Claude Code against it, stopping at each checkpoint to review the counting rules, the baselines and the results before moving on. The spec is in the repo as &lt;a href="https://github.com/HeetRanpura/stitchproof/blob/main/SPEC.md" rel="noopener noreferrer"&gt;&lt;code&gt;SPEC.md&lt;/code&gt;&lt;/a&gt;, and the counting rules are in &lt;a href="https://github.com/HeetRanpura/stitchproof/blob/main/CONVENTIONS.md" rel="noopener noreferrer"&gt;&lt;code&gt;CONVENTIONS.md&lt;/code&gt;&lt;/a&gt;, each with its own test.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Every one of these would have been impossible, or worse, with a closed API:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;I could fix the model, not just the prompt.&lt;/strong&gt; A general model, even a 120B one, can't read crochet notation reliably. A LoRA on an open 4B model, trained for about a dollar, took valid output from 78.0% to 100.0% and whole-pattern accuracy on unseen writing from 16.0% to 80.0%. You can't do that to a model you only rent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The designer's work never leaves their machine.&lt;/strong&gt; An unreleased pattern is a designer's product. With open weights on their own laptop, there's no "we don't train on your data" to trust. Turn the Wi-Fi off and StitchProof still works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A check costs nothing, so the business works.&lt;/strong&gt; Each check is electricity, not an API bill. That's what makes a cheap per-pattern price possible, and lets makers use it free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I could open it up and measure it.&lt;/strong&gt; Every model choice, prompt and parser is in the repo, and every claim above can be re-run from it. The weak spot is in the results because I could look inside and find it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No lock-in.&lt;/strong&gt; The base model, the LoRA and the GGUF files sit on disk. If a better open model comes out, swapping it in is a training run and an export, not a migration.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  My First User
&lt;/h2&gt;

&lt;p&gt;My first user is a family member who sells handmade &lt;strong&gt;hexagon cardigans and oversized patchwork sweaters on Etsy&lt;/strong&gt;, made from her own patterns.&lt;/p&gt;

&lt;p&gt;Her work shows why stitch math matters to a seller. A hexagon cardigan is dozens of identical motifs, each worked in rounds that have to land on the same count, and a patchwork sweater is panels that have to line up with their neighbours. One wrong number in her pattern doesn't cost one piece. It costs every motif made from it.&lt;/p&gt;

&lt;p&gt;She ran StitchProof on one of her own patterns, on her own laptop. The pattern never left the machine, and it isn't in the training data or in any of the results above, which were all measured on generated patterns.&lt;/p&gt;

&lt;p&gt;Her patterns also show exactly where v1 stops. Hexagons with chain-space corners and sweaters written for several sizes come back as CAN'T CHECK today, so those two are first on the list below. &lt;strong&gt;If you design or sell crochet pieces and have a draft you'd let me check, I'd love to hear from you in the comments.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;I'm entering &lt;strong&gt;Best Use of Tinker&lt;/strong&gt;, &lt;strong&gt;Best Use of Gemma&lt;/strong&gt; and &lt;strong&gt;Best Use of GitHub Copilot&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Use of Tinker
&lt;/h3&gt;

&lt;p&gt;The category asks for a fine-tune with a clear improvement over a baseline. StitchProof's case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The improvement is measured by code, not by opinion.&lt;/strong&gt; Because the model only transcribes, I can score its programs against gold programs and let the simulator count. Same prompt, same parser, same eval sets for every approach.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A real baseline, not a strawman.&lt;/strong&gt; C is the same base model with the same simulator. The only difference between C and D is the Tinker fine-tune: errors caught 75.6% → 95.6%, false alarms 42.6% → 22.2%, valid JSON 78.0% → 100.0% on held-out styles; false alarms 50.7% → 8.3% and whole-pattern accuracy 16.0% → 80.0% on another model family's writing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better on cost too:&lt;/strong&gt; about 40% fewer output tokens than the base model for the same rows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tested against overfitting on purpose.&lt;/strong&gt; Held-out notation styles, a held-out shape, and a test set written by a third model family. The one failure is reported with its exact numbers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I used Tinker for the whole loop:&lt;/strong&gt; two teacher families for training data, a third for the out-of-generator test set, every baseline, the training run, and the export check. Total: US$1.62.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The model leaves Tinker and runs anywhere.&lt;/strong&gt; Exported, converted to GGUF, and verified at 100.0% (50/50) agreement on a 4-bit base. The fused Q/K/V LoRA fix is reusable by anyone training Qwen3.5 on Tinker who wants to run it with llama.cpp.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Best Use of Gemma
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gemma 4 runs locally and does a real job no other part can:&lt;/strong&gt; reading paper. Plenty of patterns live in books and printouts, and without Gemma those users can't use StitchProof at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measured, not just wired in:&lt;/strong&gt; 98.1% (354/361) of numbers kept from phone-style photos, and 100.0% (12/12) of errors still caught end to end.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Designed around its weakness:&lt;/strong&gt; only 42.2% of photo lines come back character-perfect, so the UI shows the photo beside an editable transcription before checking, instead of trusting it blindly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fits the privacy promise:&lt;/strong&gt; &lt;code&gt;gemma4:e4b-it-qat&lt;/code&gt; through Ollama, on the same laptop. The photo is read from a temp file and deleted.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Best Use of GitHub Copilot (GitHub Actions)
&lt;/h3&gt;

&lt;p&gt;The category includes automating your project with GitHub Actions. StitchProof uses two workflows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CI on every push and pull request:&lt;/strong&gt; 149 pytest tests (simulator golden tests, one per counting rule and error code, parser robustness, generators, the API), plus frontend typecheck, Vitest and a production build. It &lt;strong&gt;fails if the committed UI build doesn't match the source&lt;/strong&gt;, so the app you run is always the app in the code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A manual eval workflow&lt;/strong&gt; (&lt;code&gt;Eval (synthetic sets)&lt;/code&gt;): it runs approaches C and D on Tinker with a hard spending cap (&lt;code&gt;--max-usd 3&lt;/code&gt; per run), writes the results table into the job summary and uploads the raw results as artifacts. It never touches the private &lt;code&gt;data/real/&lt;/code&gt; folder.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Limitations and What's Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Training data is synthetic. The only real pattern checked so far is my first user's, and it isn't part of the measured results.&lt;/li&gt;
&lt;li&gt;Notation the model hasn't seen can trip it up; "rep from * N more times" is the measured example.&lt;/li&gt;
&lt;li&gt;v1 covers US terms, amigurumi rounds and flat rows with common stitches. Charts, lace, multi-size patterns, UK terms, short rows and Tunisian crochet are CAN'T CHECK, with a reason.&lt;/li&gt;
&lt;li&gt;Designers don't all count the same way; StitchProof follows &lt;a href="https://github.com/HeetRanpura/stitchproof/blob/main/CONVENTIONS.md" rel="noopener noreferrer"&gt;&lt;code&gt;CONVENTIONS.md&lt;/code&gt;&lt;/a&gt; and always shows the numbers so a human can judge.&lt;/li&gt;
&lt;li&gt;It checks stitch math only, not gauge, fit or how the finished piece looks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Next:&lt;/strong&gt; support chain-space corners and multi-size patterns, since both show up in my first user's work; add the missing notation styles to training and test on fresh held-out ones; check more real drafts with designers; package the model for Ollama; and try the same translate, simulate, verify design on knitting.&lt;/p&gt;

&lt;p&gt;If you crochet, design patterns, or just have opinions about LLMs and arithmetic, I'd love to hear what you think in the comments.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>ai</category>
      <category>hf26challenge</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
