When the Cleanup Code Becomes the Project

Tesseract can’t do handwriting. Time to spend money.

AWS Textract. Cloud service, built-in handwriting detection, pay per page. If I’m paying for it, the output should at least be usable.

Textract

import boto3
from pathlib import Path

def extract_document(image_path):
    client = boto3.client('textract')

    with open(image_path, 'rb') as f:
        image_bytes = f.read()

    response = client.detect_document_text(
        Document={'Bytes': image_bytes}
    )

    lines = []
    for block in response['Blocks']:
        if block['BlockType'] == 'LINE':
            lines.append({
                'text': block['Text'],
                'confidence': block['Confidence']
            })

    return lines

if __name__ == "__main__":
    for image_file in sorted(Path("images").glob("*")):
        print(f"n--- {image_file.name} ---")
        for line in extract_document(image_file):
            print(f"  [{line['confidence']:5.1f}%] {line['text']}")

Confidence scores are a nice touch. Accuracy is better – maybe 40-60% on a good document.

But “better” isn’t “good enough.” “2 1/4 cups flour” comes back as “2 1/4 c fleur.” “1 tsp baking soda” becomes “1 tso bokrig sado.”

The Real Problem

Even when it gets the words right, Textract doesn’t know what any of it means. Flat text. Lines in reading order. My documents have titles, ingredient lists, instruction paragraphs, notes in margins. Textract sees none of that. Just characters on a page.

So now I’m writing parsers.

import boto3
import json
import re
from pathlib import Path

def extract_document(image_path):
    client = boto3.client('textract')

    with open(image_path, 'rb') as f:
        image_bytes = f.read()

    response = client.detect_document_text(
        Document={'Bytes': image_bytes}
    )

    lines = []
    for block in response['Blocks']:
        if block['BlockType'] == 'LINE':
            lines.append({
                'text': block['Text'],
                'confidence': block['Confidence']
            })

    return lines

def parse_structured_data(raw_lines):
    title = None
    items = []
    instructions = []

    quantity_pattern = r'^(d+[s/]*d*)s*(cups?|tbsp?|tsp|oz|lbs?|g|ml|c)s+(.+)'

    for line in raw_lines:
        text = line['text'].strip()
        match = re.match(quantity_pattern, text, re.IGNORECASE)

        if match:
            items.append({
                'quantity': match.group(1),
                'unit': match.group(2),
                'item': match.group(3)
            })
        elif not title:
            title = text
        else:
            instructions.append(text)

    return {'title': title, 'items': items, 'instructions': instructions}

if __name__ == "__main__":
    for image_file in sorted(Path("images").glob("*")):
        print(f"n--- {image_file.name} ---")
        raw_lines = extract_document(image_file)
        result = parse_structured_data(raw_lines)
        print(json.dumps(result, indent=2))

Works on 30% of the documents. The other 70% break at least one assumption. Title not on line one. Quantities written backwards. Abbreviations I’ve never seen. Crossed-out text mixed into the content. Multi-line entries split apart.

Every new document, a new edge case. Every new edge case, another if, another regex.

Saturday Night

Here’s where I’m at:

  • Pre-processing with 6 configurable parameters
  • 200+ lines of regex and heuristics
  • 70% of documents still need a human
  • Accuracy I’m being generous calling 30%

The parser is now more work than just typing things by hand.

And every time I fix one document’s output, three others break. The heuristics are fragile. Interconnected. Basically untestable because no two documents look alike.

One document has a crossed-out line. Original text scratched out, correction written above. Any person glances at it and reads the correction. Half a second.

Textract returns both lines. Jumbled. My parser doesn’t know what a strikethrough is. Teaching it would mean analyzing the spatial layout of ink strokes. That’s not a text problem anymore. That’s a computer vision problem.

I’m a full day in. The system I’m building reads worse than I do, and the code to make it slightly less bad is growing faster than the documents it’s supposed to process.

Rockpack 8.0 – A React Scaffolder Built for the Age of AI-Assisted Development

Every developer using Claude Code or Cursor has hit the same wall at some point.

You spin up a new project, start vibing with the AI, and for the first hour everything feels like magic. Then slowly things start going sideways. The agent reformats a file it had no business touching. It runs the entire test suite to check a two-line change. It invents a new folder structure that contradicts the one you established three days ago. It adds an abstraction layer you didn’t ask for and now have to maintain.

None of this is the AI’s fault. It’s working with what you gave it.

The uncomfortable truth about AI coding tools

AI agents don’t have opinions. They infer conventions from context. If your project has strict ESLint rules, the agent follows them. If your project has no rules, the agent makes them up – differently every session.

This means the single biggest lever you have over AI code quality isn’t your prompt. It’s your project setup.

I spent months figuring this out the hard way, and the pattern became clear:

Lint rules act as a spec. When ESLint is configured with sonarjs, unicorn, and perfectionist, the agent produces code that passes those rules – or the pipeline rejects it and the agent fixes itself. Either way, low-quality code doesn’t accumulate.

Tests change agent behavior. A project with Jest already configured gets test coverage by default. The agent writes tests because the infrastructure is there. Without it, tests are always “coming later” -and later never comes.

Architecture is contagious. Consistent folder structure and naming conventions propagate. The agent sees the pattern and follows it. An inconsistent codebase produces inconsistent AI output.

Token waste is a setup problem. By default, Claude Code reads everything it can find, runs the broadest possible test suite, and touches adjacent code “just to be safe.” This is expensive and produces

noisy diffs. A well-written CLAUDE.md fixes most of it.

What CLAUDE.md actually changes

CLAUDE.md is a file Claude Code reads at the start of every session. It’s essentially a standing instruction set for the agent – and it has a measurable effect on how the agent operates.

Here’s what made the biggest difference in practice:

Cost Saving Rules

  • Run only affected tests when possible
  • If only one test file was modified, run only that file
  • Read only files relevant to the current task
  • Avoid scanning the entire repository unless necessary

Code Rules

  • Don’t add features or abstractions beyond what the task requires
  • Prefer minimal diffs
  • Avoid reformatting unrelated code
  • Preserve existing architecture and conventions

After adding these rules, the diffs got smaller, token usage dropped, and reviews became faster. The agent stopped “helpfully” refactoring things that were working fine.

Rockpack 8.0

I’ve been maintaining Rockpack for several years – a CLI scaffolder for React projects. Version 8.0 is the first release built with AI-assisted development as a
first-class concern.

One command:

  npm i -g @rockpack/starter
  rockpack my-app

What you get:

  • React 19 + TypeScript 6 with strict configuration
  • Webpack 5 – CSR, SSR, library builds, bundle analysis out of the box
  • ESLint 10 flat config – sonarjs, unicorn, perfectionist, no-only-tests, import-lite, Prettier integrated
  • Pre-commit hooks – lint runs before every commit, bad code physically cannot get in
  • Jest pre-configured with TypeScript support
  • Feature-based project structure – consistent from the first file
  • Optimized CLAUDE.md – included in every generated project

Supported project types: React SPA, React SPA + SSR, React Component (npm-ready), UMD Library.

Why this matters now

A year ago, “AI-friendly project setup” wasn’t a thing people thought about. Today it’s the difference between an AI that saves you hours and one that creates extra work.

The tools are good. The gap is the environment they work in.

Rockpack closes that gap in a few minutes instead of a few days.

https://github.com/AlexSergey/rockpack – MIT license.

If you’re actively using Claude Code or Cursor, I’d be genuinely curious what’s in your CLAUDE.md. There’s no established best practice yet and the community is still figuring this out.

Mismanaging the Treasure Hunt Engine in Hytale Servers Will Get You Killed

The Problem We Were Actually Solving

It was our third server migration in three months, and our newly minted engineer, Alex, was frantically calling me from his desk. “Our server’s lagging, Chris, and it’s all because of the event handling!” His words were laced with panic. The users were complaining about slow response times and dropped events. We’d deployed the standard out-of-the-box event handling configuration from Veltrix, but somehow it wasn’t coping with our rapidly expanding server.

As I analyzed our Prometheus metrics, my anxiety only amplified – we were at 98% CPU utilization, and the error rate for our events service had shot up to 2.5%. We needed to act fast before our users deserted us in droves.

What We Tried First (And Why It Failed)

We decided to opt for the time-tested ‘EventBatching’ approach to alleviate some of the pressure off our CPU. It was a straightforward technique where the client-side events would be batched together before being sent to our server. This would theoretically reduce the number of events we’d need to process, thus cutting down on CPU usage. However, we hit a problem when we realized that this approach was making it difficult for us to identify and tackle the root issues of the problem. Without real-time event processing, we struggled to pinpoint the events that were causing the server to be overwhelmed.

A few days into our experiment, we encountered another problem: the client-side batching introduced an unacceptable delay in event processing, which, in turn, gave us an average latency of around 10 seconds for the events service.

The Architecture Decision

We decided to shift our focus towards more scalable event handling, using an Event Sourcing approach. This allowed us to offload the complex event processing to our storage solution, AWS DynamoDB, and decouple it from the CPU-intensive processing on our server. This approach not only solved our CPU utilization problem but also allowed us to handle an enormous amount of events without incurring significant delays.

We used the AWS DynamoDB streams and Fan-out Queue design pattern to offload the events into batches and eventually process them. This provided us with a seamless real-time experience, without compromising on the high availability and scalability that our users expected from us.

What The Numbers Said After

Our server CPU utilization dropped by 25%, and the error rate for our events service plummeted to 0.2%. This gave us the much-needed breathing room to further optimize our system. The latency for our events service decreased to a whopping 200 ms, allowing users to experience a seamless gaming experience.

What I Would Do Differently

Looking back, I would suggest more comprehensive stress testing for the EventBatching approach before deciding on it as the primary solution. Also, we didn’t initially benchmark the client-side batching, which could have given us a clearer picture of its performance implications.

In hindsight, while the Event Sourcing approach was a resounding success, it would be beneficial to explore other configuration and design patterns, such as ‘Event Meshes’ or ‘Kafka Streams’, that could further improve our system’s performance and scalability. This would help us avoid potential bottlenecks and anticipate future requirements before they become critical issues.

Why Hardcoded Automations Fail AI Agents

DoSync Concepts — Part 2 of 5

There’s a rule every smart home developer has written at least once:

if motion_detected and time > 22:00:
    turn_on(hallway_light)

It works. It’s simple. And it’s the foundation of how every major home automation platform thinks about intelligence today.
The problem isn’t that the rule is wrong. The problem is what happens when you add an AI agent to the system — and why rules, no matter how many you write, are the wrong abstraction for that job.

The rule-writing problem
Rules are commands in disguise. “If X happens, do Y” is just a delayed command — the human still decided what Y should be, they just decided it in advance.
That works perfectly when you can anticipate every scenario, your device set never changes, and the AI’s job is simply to trigger pre-approved responses. None of those assumptions hold in a real AI-augmented environment.
Consider what happens when you add a new device. A smart lock arrives. You register it with your platform. Now you have to go back through every relevant rule and add the lock. Miss one and the system silently does nothing in exactly the scenario where you needed it most.
Or consider an AI that detects unusual temperature patterns in the kitchen — a sensor reading that might mean the fridge compressor is failing. The AI knows something is wrong. But there’s no rule for “fridge compressor anomaly,” because nobody wrote one. The system does nothing.
This is the brittleness problem: hardcoded automations only respond to situations that were anticipated when the rule was written.

Why AI agents make this worse, not better
The intuitive solution is to write more rules, or smarter rules. Use the AI to generate them. Inspect logs and auto-suggest new automations.
But this misses the architectural problem.
An AI agent doesn’t produce rules. It produces understanding. When a vision model detects a fall, it doesn’t know it needs to call phone.call(“911”) and lock.unlock() and alarm.activate(“emergency”). It knows there’s an emergency. The translation from understanding to commands still has to be written somewhere — by someone — in advance.
The more capable the AI, the more situations it can detect. And every new situation the AI can detect is a new set of rules that needs to be written to respond to it. The rule surface grows faster than anyone can maintain it.

What capability-based discovery changes
The alternative is to shift where the knowledge lives.
Instead of centralizing the response logic in rules, distribute it to the devices themselves. Each device declares what it can do and in what contexts it’s appropriate to act:

json{
  "device_id": "lock-frontdoor-01",
  "tags": ["door-lock", "entrance", "emergency"],
  "actuators": [
    { "type": "unlock", "emergency_capable": true }
  ],
  "emergency_capable": true
}

This manifest is not a rule. It’s a declaration. The device is saying: I can unlock. I’m relevant in emergencies. I’m at the entrance.
When the AI fires a semantic intent — ensure_safety / emergency — the resolver reads every registered manifest and asks: which devices declared themselves relevant to this situation? It builds the response at runtime, from what’s actually available.
ensure_safety [emergency]
→ lock-frontdoor-01 unlock (emergency_capable, entrance tag)
→ alarm-main-01 activate (emergency_capable)
→ wiz-living-01..10 turn_on (light tag, full brightness)
→ notifier-sms-01 notify (communication tag)
All in parallel. No rules written for this scenario.
Add the smart lock tomorrow — it participates automatically in every relevant scenario. No rule rewriting. No code changes. The device brought its own knowledge.

The difference that matters
Hardcoded automations are fragile because they encode knowledge in a central place that can’t keep up with change. Every new device, every new scenario, every new AI capability requires human intervention to update the rules.
Capability-based discovery distributes that knowledge to the edges. Devices own their context. The system is resilient to change because adding new capabilities is additive, not a rewrite.
For AI agents specifically, this is the difference between a system that only responds to situations you anticipated and a system that responds to situations the AI can detect — whether you anticipated them or not.

GitHub: https://github.com/giulianireg-spec/dosync-protocol
License: Apache 2.0

DoSync Concepts is a series exploring the ideas behind the DoSync Protocol — the semantic layer between AI agents and physical systems.