<?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: Isamu Arimoto</title>
    <description>The latest articles on DEV Community by Isamu Arimoto (@isamu).</description>
    <link>https://dev.to/isamu</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%2F4043370%2F2e4876ea-5961-4167-9592-045173eb6fef.jpg</url>
      <title>DEV Community: Isamu Arimoto</title>
      <link>https://dev.to/isamu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/isamu"/>
    <language>en</language>
    <item>
      <title>The Missing Bottom Layer: Why AI Isn't Making Your Company Faster</title>
      <dc:creator>Isamu Arimoto</dc:creator>
      <pubDate>Sat, 10 Oct 2026 22:46:51 +0000</pubDate>
      <link>https://dev.to/isamu/the-missing-bottom-layer-why-ai-isnt-making-your-company-faster-2358</link>
      <guid>https://dev.to/isamu/the-missing-bottom-layer-why-ai-isnt-making-your-company-faster-2358</guid>
      <description>&lt;p&gt;&lt;em&gt;A "business harness": minimize human intervention, keep quality constant.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is an English adaptation of a Japanese article published on the &lt;a href="https://singularitysociety.org/articles/blog/2026-10-11-business-harness/" rel="noopener noreferrer"&gt;Singularity Society site&lt;/a&gt;. It grew out of a talk, "Living at 500 commits a day with MulmoTerminal" (2026-09-06), whose second half was about the three-layer harness that lets AI write software without a human reading every change. The examples here are rewritten for US-style business practice. Everything from section 4 onward is a hypothesis we have not yet validated. Times and counts are illustrative, not measured.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contents&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Drafting takes 30 minutes now. Reading hasn't gotten any faster&lt;/li&gt;
&lt;li&gt;The bottleneck moved to "after drafting"&lt;/li&gt;
&lt;li&gt;What already happens in software development&lt;/li&gt;
&lt;li&gt;What a business harness is&lt;/li&gt;
&lt;li&gt;What's missing from the tools you already have&lt;/li&gt;
&lt;li&gt;The core idea: make the AI produce fields, not prose&lt;/li&gt;
&lt;li&gt;Five scenes: inbound lead, business trip, client dinner, business case, vendor invoice&lt;/li&gt;
&lt;li&gt;Turning human judgment into a mechanism&lt;/li&gt;
&lt;li&gt;Where to start&lt;/li&gt;
&lt;li&gt;Open questions&lt;/li&gt;
&lt;li&gt;Build the layer, not the app&lt;/li&gt;
&lt;li&gt;Sidebar: the software vocabulary&lt;/li&gt;
&lt;li&gt;Appendix: glossary, sources&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. Drafting takes 30 minutes now. Reading hasn't gotten any faster
&lt;/h2&gt;

&lt;p&gt;An account executive drafts a proposal with a generative AI. What used to take half a day takes 30 minutes. So far, this is happening everywhere.&lt;/p&gt;

&lt;p&gt;The problem starts after that. The draft goes to the VP of Sales, who reads the whole thing. The pricing section is thin, so it comes back. The AE fixes it and resubmits; the VP reads the whole thing again. It goes to Legal, who reads the whole thing. The assumptions section is missing, so it comes back. Fix, resubmit, and the VP reads it a third time. The customer gets it four to five business days later. Humans have read it end to end five times.&lt;/p&gt;

&lt;p&gt;AI sped up one step, "drafting." The "checking" steps have not moved an inch. And because AI can now produce more drafts, the checking side is busier than before.&lt;/p&gt;

&lt;p&gt;This is not a model problem. It is not a prompting problem. &lt;strong&gt;There is no mechanism in the business workflow that decides, before a human looks at it, whether an AI-made artifact is stopped or passed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Software development already has that mechanism. This article calls it a &lt;strong&gt;harness&lt;/strong&gt; and asks what it would take to port it to business work.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The bottleneck moved to "after drafting"
&lt;/h2&gt;

&lt;p&gt;Replay the proposal story by what humans physically did.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read&lt;/strong&gt; — the VP three times, Legal once, the AE once&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; — the AE twice&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Carry&lt;/strong&gt; — the AE emailed it to the VP, to Legal, to the customer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search&lt;/strong&gt; — next month, a different AE gets the same pushback and digs through old email threads to find out why&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI did only "draft." Everything else ran on human hands, at human speed. However fast the AI gets, the whole thing tops out at "how fast a human reads."&lt;/p&gt;

&lt;p&gt;Two numbers run through the rest of this article.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Human interventions&lt;/strong&gt; — how many times a human read, fixed or carried the artifact before it reached its recipient. In the story above: read 5, fix 2, carry 3, so 10&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decision split&lt;/strong&gt; — of the decisions made about that artifact, what share was made by a machine, by an AI, and by a human. Above: humans made 100%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The harness exists to move those two numbers: interventions down to the entry and the exit, decisions shifted toward the machine. Quality is measured separately, by post-delivery returns, corrections and incidents. If you cut interventions and those go up, you are doing it wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. What already happens in software development
&lt;/h2&gt;

&lt;p&gt;Software teams hit this problem first, when AI started writing code, and answered it first. In the author's environment, AI-written changes are checked and merged a few hundred times a day. No human reads all of them; nobody even tries.&lt;/p&gt;

&lt;p&gt;Broken changes still don't get in, because there are &lt;strong&gt;machine checks between the steps, and when a check fails the AI is sent back to redo the work.&lt;/strong&gt; The flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A human writes what they want, one ticket per request. Rough is fine; the AI tidies it&lt;/li&gt;
&lt;li&gt;The AI makes the change&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Machines check it.&lt;/strong&gt; Style rules, type consistency, each unit's behavior, the end-to-end path. If anything fails, the AI fixes it and re-runs. No human looks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A different AI reads it and comments.&lt;/strong&gt; The AI fixes. This loops automatically until the reviewer is satisfied&lt;/li&gt;
&lt;li&gt;What reaches a human is "a change that passed everything" plus "the points the AI wants a human to rule on"&lt;/li&gt;
&lt;li&gt;A human decides whether to merge. Minutes&lt;/li&gt;
&lt;li&gt;When the same comment appears twice, it becomes a machine rule. After that, neither humans nor AIs ever see it again&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Humans touch steps 1 and 6. Most decisions are made by machines; the AI's job is to keep reworking until the machines pass it. Requests, changes, check results, comments, decisions and history all live in one place (GitHub).&lt;/p&gt;

&lt;p&gt;The important part is the shape, not the tool names. &lt;strong&gt;Machines stop. AI drafts and flags. Humans see only exceptions. Everything runs in one place, and the same comment never reaches a human twice.&lt;/strong&gt; The tools are in the sidebar at the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. What a business harness is
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Definition
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;business harness&lt;/strong&gt; is the control system that lets an AI agent &lt;strong&gt;produce output of consistent quality while cutting human intervention to the minimum.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI behavior is probabilistic. The same request produces different output every time; one day the assumptions are missing, another day a number has no source. A harness &lt;strong&gt;wraps that variance in deterministic constraints and increases the number of decisions made by machines&lt;/strong&gt;, so that the output stabilizes even though the AI does not. You don't make the AI smarter. You harden what surrounds it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three layers
&lt;/h3&gt;

&lt;p&gt;Control is organized in three layers. The lower the layer, the wider and the stronger.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Nature of its judgment&lt;/th&gt;
&lt;th&gt;Authority&lt;/th&gt;
&lt;th&gt;Software counterpart&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Machine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Stop&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Same input, same answer, every time (deterministic)&lt;/td&gt;
&lt;td&gt;Decides pass / fail&lt;/td&gt;
&lt;td&gt;lint, type checks, unit tests, end-to-end tests, CI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Draft, flag&lt;/td&gt;
&lt;td&gt;Same input, different answer (probabilistic)&lt;/td&gt;
&lt;td&gt;Redo, attach concerns. &lt;strong&gt;No authority to stop&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;code generation, AI code review, cross-review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Human&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Notice, decide&lt;/td&gt;
&lt;td&gt;Understands anything, but slow and headcount-bound&lt;/td&gt;
&lt;td&gt;Final call, exceptions, policy&lt;/td&gt;
&lt;td&gt;PR merge decision; MulmoTerminal for noticing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Only the deterministic layer, the machine, can hold the authority to stop.&lt;/strong&gt; AI judgment fluctuates, so it cannot be the gate. Make it the gate and the same proposal passes on Monday and fails on Tuesday. Nobody follows a verdict that cannot be explained.&lt;/p&gt;

&lt;p&gt;So what does the AI do? It &lt;strong&gt;drafts&lt;/strong&gt; and it &lt;strong&gt;flags&lt;/strong&gt;. There is a step where an AI "approves," but it is not a gate: two AIs trade comments and fixes to reduce the number of open points that reach a human. Whatever the AI did not sign off on is attached as a "concern," and the human decides.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ten stages
&lt;/h3&gt;

&lt;p&gt;Where do the controls go? Split the workflow into ten stages; every stage has a control point, "who decides here."&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;Who decides&lt;/th&gt;
&lt;th&gt;Software counterpart&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;① Request&lt;/td&gt;
&lt;td&gt;Record who wants what, by when, in what form&lt;/td&gt;
&lt;td&gt;Human asks, AI tidies&lt;/td&gt;
&lt;td&gt;Issue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;② Context&lt;/td&gt;
&lt;td&gt;Hand the AI the company standards, procedures, policies, history, budget, related records&lt;/td&gt;
&lt;td&gt;Machine keeps one source; AI reads&lt;/td&gt;
&lt;td&gt;CLAUDE.md / skills / the repo's code and design docs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;③ Draft&lt;/td&gt;
&lt;td&gt;Produce the artifact&lt;/td&gt;
&lt;td&gt;AI&lt;/td&gt;
&lt;td&gt;AI writes code, opens a PR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;④ Machine check&lt;/td&gt;
&lt;td&gt;Structure, consistency, prohibited content, cross-references&lt;/td&gt;
&lt;td&gt;Machine&lt;/td&gt;
&lt;td&gt;lint / types / unit / e2e / duplication, run in CI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⑤ AI review&lt;/td&gt;
&lt;td&gt;A second AI reads, comments, the first fixes; loops until satisfied&lt;/td&gt;
&lt;td&gt;AI (never stops)&lt;/td&gt;
&lt;td&gt;AI code review → fix → LGTM loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⑥ Human decision&lt;/td&gt;
&lt;td&gt;Humans see only what passed, plus the AI's concerns&lt;/td&gt;
&lt;td&gt;Human&lt;/td&gt;
&lt;td&gt;PR merge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⑦ Send / execute&lt;/td&gt;
&lt;td&gt;Send to the customer, sign, pay. The irreversible point&lt;/td&gt;
&lt;td&gt;Machine runs the final check and executes; the human's "OK to send" was given in ⑥&lt;/td&gt;
&lt;td&gt;merge → release / deploy; branch protection blocks unverified merges&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⑧ Record&lt;/td&gt;
&lt;td&gt;Who decided what, when, on which version&lt;/td&gt;
&lt;td&gt;Machine&lt;/td&gt;
&lt;td&gt;GitHub: issue, PR, reviews, CI results, history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⑨ Visibility and alerts&lt;/td&gt;
&lt;td&gt;Where things are stuck and how many are waiting, across all document types&lt;/td&gt;
&lt;td&gt;Machine shows; humans notice&lt;/td&gt;
&lt;td&gt;MulmoTerminal / notifications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⑩ Improvement&lt;/td&gt;
&lt;td&gt;Count returns and comments; turn repeats into rules&lt;/td&gt;
&lt;td&gt;Machine counts; humans decide&lt;/td&gt;
&lt;td&gt;same comment twice → lint rule / test (warn → error)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The ten stages need &lt;strong&gt;one place where they all live (a hub)&lt;/strong&gt;. If requests, artifacts, check results, decisions and history sit in different systems, humans end up carrying things between stages.&lt;/p&gt;

&lt;p&gt;The harness's job is to &lt;strong&gt;keep pushing decisions downward&lt;/strong&gt;: the points humans used to find by reading go to the AI's flags; whatever can be written as a rule goes to the machine. The further down, the fewer the interventions and the smaller the variance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not only stopping: surfacing
&lt;/h3&gt;

&lt;p&gt;Checking is only half of the harness. Stage ② is not just "hand over the company standards." It is &lt;strong&gt;find every record relevant to this piece of work, including the ones the human has forgotten, and put them in front of the AI.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before a client visit: when did we last meet this contact, what did we discuss, what did we bring? At the dinner, what did their VP order and what did she avoid? What has their company announced in the last month? If a web inquiry turned into a deal: the original inquiry text, the SDR's qualification notes, the call transcript, past proposals and quotes, their latest 10-K.&lt;/p&gt;

&lt;p&gt;Humans forget. When the account owner changes, it all disappears. Machines don't forget. As long as the records are in the hub, the AI reads them and &lt;strong&gt;becomes the one proposing the next move&lt;/strong&gt;: a gift suggestion, the skeleton of a business case, the first draft of a quote, the trip report. The time humans spent remembering, searching and asking around goes away.&lt;/p&gt;

&lt;p&gt;Stopping (④) and surfacing (②③) run on the same data in the same hub. One without the other does not connect. The records you structured for checking are the same records that feed the proposals.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. What's missing from the tools you already have
&lt;/h2&gt;

&lt;p&gt;Businesses are not short of parts. If anything they have too many.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Drafting&lt;/strong&gt; — ChatGPT, Claude, Gemini, Microsoft 365 Copilot, Gemini for Google Workspace, Notion AI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Saved instructions&lt;/strong&gt; — custom instructions, GPTs, Claude Projects, Copilot Studio agents, the internal prompt library&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI review&lt;/strong&gt; — contract lifecycle management with AI playbooks (Ironclad, Evisort and others) that flag missing clauses and off-policy language and suggest fallbacks. Copilot's and Grammarly's document review&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human approval&lt;/strong&gt; — approval workflows in Salesforce, NetSuite, ServiceNow or Jira; delegation-of-authority matrices that say who can approve how much&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Send and sign&lt;/strong&gt; — e-signature (DocuSign and others), increasingly bundled with contract management and review&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Machine checks&lt;/strong&gt; — field validation in Salesforce and ERPs; three-way matching (PO, receipt, invoice) in accounts payable; expense platforms (Ramp, Concur, Expensify) that flag or, in Ramp's case, block out-of-policy spend at the point of purchase; data loss prevention (Microsoft Purview and others) that detects and blocks sensitive data across email, drives and chat&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these works inside its own territory. What's missing is not any one feature. It is &lt;strong&gt;four connections.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gap 1: The stages are not connected and don't live in one place.&lt;/strong&gt;&lt;br&gt;
The request is in Slack, the context in Google Drive, the draft in a chat window, the approval in Salesforce, the delivery in email, the record in each system's own log. Every stage is in a different place, so humans carry things between them. The carrier's speed becomes the company's speed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gap 2: The machine layer is thin for documents.&lt;/strong&gt;&lt;br&gt;
Field validation works on data typed into a system, not on a proposal written in Word or Google Docs. Three-way match works on invoices, not on quotes. Expense policy engines work on card transactions, not on the SOW. DLP catches credit card numbers, not "the assumptions section is missing" or "a price marked tentative is written as final." And documents are exactly what AI now produces in volume.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gap 3: There is no path from a return to a rule.&lt;/strong&gt;&lt;br&gt;
The reason for a pushback ends in an email to the author. Nothing counts, so nobody notices that the same comment has now appeared twice. Without this path, the number of items reaching humans never goes down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gap 4: No cross-cutting visibility.&lt;/strong&gt;&lt;br&gt;
You can see your own approval queue. You cannot see, across proposals, contracts, business cases and invoices, how many items are stuck where and for how many days.&lt;/p&gt;

&lt;p&gt;The four are not independent. Without a hub (Gap 1) you cannot build visibility (Gap 4); without a machine layer (Gap 2) a new rule (Gap 3) has nowhere to land. &lt;strong&gt;So buying more parts does not connect them.&lt;/strong&gt; This is not about replacing the products you have. It is about the "bottom layer" that wires them into one flow of control.&lt;/p&gt;
&lt;h3&gt;
  
  
  The condition for wiring: everything is data, everything runs through an API
&lt;/h3&gt;

&lt;p&gt;That fixes a selection criterion for tools.&lt;/p&gt;

&lt;p&gt;A harness is a mechanism where machines and AIs, not humans, carry work between stages. Machines and AIs can only drive tools that expose an &lt;strong&gt;API&lt;/strong&gt; (a connection point for operating the tool from outside, without a human clicking through a screen). A tool that only moves when a human clicks forces a human into the stage. That tool sits outside the harness.&lt;/p&gt;

&lt;p&gt;Three rules follow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Everything becomes data.&lt;/strong&gt; Requests, artifact fields, check results, decisions and reasons, delivery records. Also policies, budgets and approval records, and agreements made in email or chat. Not prose or screens for humans, but data machines and AIs can read and write&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything runs through an API.&lt;/strong&gt; Drafting, checking, approving, sending, recording: all callable by machines and AIs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tools without an API don't go in the harness.&lt;/strong&gt; However good the feature, if a human has to operate it, it cannot reduce human interventions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The role of the GUI follows too. &lt;strong&gt;A screen is a viewer for the human's final check.&lt;/strong&gt; The human presses a minimal set of buttons: pass, return, OK to send. A product that piles features into its screen is designed for humans to drive the workflow; it points the opposite way from a harness, and the more convenient it looks, the more firmly it locks human intervention in place.&lt;/p&gt;

&lt;p&gt;By this criterion, only tools with data and APIs can sit on the ten stages. Keep the approval system and the e-signature tool if they connect through an API; replace them if they don't.&lt;/p&gt;
&lt;h2&gt;
  
  
  6. The core idea: make the AI produce fields, not prose
&lt;/h2&gt;

&lt;p&gt;Building a machine layer for documents takes one change of mindset. &lt;strong&gt;Stop asking the AI to write a document. Ask it to produce fields.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Make the fields the source of truth
&lt;/h3&gt;

&lt;p&gt;Today: ask the AI to "write the proposal," get prose, a human reads it.&lt;br&gt;
Instead: ask the AI to "produce the proposal's &lt;strong&gt;contents as fields&lt;/strong&gt;." Problem, approach, scope, price, assumptions, and the source for every number come out as fields. Machines check the fields; only what passes is rendered into prose.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;quote    = fields (line items, unit price, quantity, terms, valid-until) + rendering
proposal = fields (problem, approach, scope, price, assumptions)        + rendering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fields are the truth; the prose is their output.&lt;/strong&gt; Four things follow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Checks become writable&lt;/strong&gt; — "at least one price," "every decision has an owner and a due date" are rules over fields. No reading comprehension required&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Changes become meaningful&lt;/strong&gt; — "what changed since the last version" is no longer a text diff but "the price changed," "one assumption was added." That is all a decision-maker needs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contradictions become impossible&lt;/strong&gt; — if the proposal and the quote reference the same price field, they cannot disagree&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wording changes don't break checks&lt;/strong&gt; — the AI phrases things differently every time. Compare fields and a check fails only when the meaning changed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't run it backwards. "Write prose → have an AI extract fields → check" means the extraction fluctuates, so you are checking an estimate. "Produce fields → check → render prose" means the thing being checked is the truth. &lt;strong&gt;Use the AI to draft, not to judge pass or fail.&lt;/strong&gt; Drafting may fluctuate; if it fails, send it back until it passes.&lt;/p&gt;

&lt;h3&gt;
  
  
  What machines can check, and what stays with the AI
&lt;/h3&gt;

&lt;p&gt;Draw the line honestly. &lt;strong&gt;What a machine can check deterministically is field-to-field matching.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Machine, deterministic&lt;/th&gt;
&lt;th&gt;AI, probabilistic&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Are the required fields present (structure)&lt;/td&gt;
&lt;td&gt;Is the content sound (is the price right, is a fourth assumption missing)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Do fields agree — proposal price = quote price, cover total = line-item total, start date &amp;lt; end date (consistency)&lt;/td&gt;
&lt;td&gt;Does the argument hold together, is it persuasive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is the source field filled and does the reference resolve (source present)&lt;/td&gt;
&lt;td&gt;Is that source a valid basis for that number (source validity)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No prohibited terms; recipient and version match what was approved (prohibitions, identity)&lt;/td&gt;
&lt;td&gt;Will it land with this reader; is the tone right&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Do outside records agree — is it tied to an approved budget line; do invoice items and amounts match the PO, receipt and contract; does the expense carry amount, time, place, purpose and attendees and sit under policy caps; does it match the terms agreed in the deal record (cross-reference)&lt;/td&gt;
&lt;td&gt;Reading an agreement out of free-form email or chat; judging whether an exception is justified in the spirit of a policy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The left column gives the same answer whoever looks, so it can be delegated to a machine, and the moment it is, that angle disappears from human review. The right column fluctuates, so the AI flags but never stops. The final word stays with a human.&lt;/p&gt;

&lt;h3&gt;
  
  
  Checking against facts outside the document
&lt;/h3&gt;

&lt;p&gt;Checking a document on its own does not make it a correct document. An invoice is correct only when it matches the PO, the receipt and the contract. A business case can be approved only when it is tied to an approved budget line. A proposal's price must match what was agreed with the customer in email or on the call. An expense passes only when who, with whom, for what purpose and how much are explicit and within policy.&lt;/p&gt;

&lt;p&gt;So the check runs not only inside the document but against four kinds of facts outside it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;History&lt;/strong&gt; — prices, dates and scope agreed with the customer or internally in email, chat, calls and meetings. For a business case, the meeting where it was decided&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Policy&lt;/strong&gt; — the T&amp;amp;E policy, the delegation-of-authority matrix, pricing and discount rules, the gift policy, the conditions that trigger Legal review&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Related records&lt;/strong&gt; — quote, PO, receipt, contract (MSA/SOW), previous meeting notes, past business cases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan and budget&lt;/strong&gt; — the approved annual operating plan and its remaining balance, the OKRs or targets it serves, the approval records&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why section 5 said "everything becomes data." While the policy lives in a PDF, the agreement lives in an email and the budget lives in a spreadsheet, cross-referencing is a human job. Once they are data in one place, it is a machine job.&lt;/p&gt;

&lt;h3&gt;
  
  
  The evidence chain: no document stands alone
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;From here to the end of section 6 is about the internals. If you only need to decide whether to try this, read the scenes in section 7 and the steps in section 9 first, and come back to these tables when you implement.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Company documents are built on earlier documents. A typical chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;meeting decision → business case → spending approval → PO → receipt → invoice → payment → expense report
       ↑               ↑                 ↑                                              ↑
   OKRs / AOP     approved budget   quote / contract                       pre-approval / budget
   (strategy)     (line + balance)  (MSA / SOW)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The horizontal arrows are document order; the vertical arrows are the evidence that supports each document from below. The annual budget is not on the main line. It was approved at the start of the year, and it supports the business case, the spending approval and the PO as an existing frame. Every arrow is a &lt;strong&gt;reference&lt;/strong&gt;. The business case carries the meeting-decision ID and the budget line; the spending approval carries the business case ID and the budget ID; the invoice carries the PO number and the receipt number. When references are IDs, a machine can verify the same five things for every one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Existence&lt;/strong&gt; — the referenced record exists&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status&lt;/strong&gt; — it is approved and in force. Not a rejected case, an expired quote, or last year's closed budget&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Values&lt;/strong&gt; — amounts, quantities and periods match; for a budget, the balance covers it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Period&lt;/strong&gt; — the fiscal year matches. This year's case is not pointing at last year's line&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authority&lt;/strong&gt; — the requester and approvers are allowed, per the delegation-of-authority matrix, to raise and approve this; the body that made the decision had the power to make it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Per document, the evidence to reference and what the machine verifies:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Document&lt;/th&gt;
&lt;th&gt;Evidence to reference&lt;/th&gt;
&lt;th&gt;What the machine verifies&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Business case&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The meeting decision that spawned it. The AOP line and its balance. The OKR it serves. Related past initiatives. Sign-off from affected teams&lt;/td&gt;
&lt;td&gt;The decision includes "build a case," owner matches the author. The budget line exists, is approved for this fiscal year, and the balance covers the ask. The OKR link is a field. If a similar initiative ran before, its outcome is referenced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Spending approval&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The approved business case. The budget ID. The attached quote. The DoA matrix&lt;/td&gt;
&lt;td&gt;Case is approved. Budget is current-year and sufficient. Amount matches the quote. Routed to the approver the DoA requires for this amount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Proposal / SOW&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The opportunity record. Agreed price, timeline and scope. The price book version. Discount approval&lt;/td&gt;
&lt;td&gt;Matches the agreed terms. Price book is current. Discount within policy, or deal-desk approval is attached&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Quote&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The proposal or opportunity. Price book. Discount approval. Previous quote&lt;/td&gt;
&lt;td&gt;Price matches the proposal. The delta from the previous quote is tied to a recorded agreement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Contract (MSA / SOW)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Proposal and quote. The standard clause library version. The Legal review record&lt;/td&gt;
&lt;td&gt;Price and scope match. Clauses are from the current library. Deviations carry an approval. If Legal-review triggers apply, review is recorded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Purchase order&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The approved spending request. The quote. The budget&lt;/td&gt;
&lt;td&gt;Approval is in force. Amount matches the quote. Budget balance covers it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Vendor invoice&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The PO. The goods or service receipt. The contract's payment terms. The vendor master&lt;/td&gt;
&lt;td&gt;Items and amounts match the PO. Receipt is complete. Due date matches the contract. Bank details match the vendor master, and any change has a verified callback on file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Expense report&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pre-trip approval or spending approval. Budget. T&amp;amp;E policy. The vendor/contact record. The opportunity or initiative it served&lt;/td&gt;
&lt;td&gt;Amount, time, place, business purpose and attendees are all present. Matches the pre-approval. Within budget. Within per-diem and policy caps. Attendees exist in the CRM and the purpose links to an opportunity or initiative ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Meeting notes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Previous notes. The body's authority. Attendee records&lt;/td&gt;
&lt;td&gt;Previous decisions carry a status. Decisions above the DoA threshold link to a spending approval. The body that decided had the authority to&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Everything in the right column is matching of IDs, statuses, values, periods, authority and field values, which a machine can do deterministically. "Where's the evidence?" "Is there budget for this?" "Which meeting decided that?" — the questions people ask out loud today — all land here.&lt;/p&gt;

&lt;h3&gt;
  
  
  How references get created
&lt;/h3&gt;

&lt;p&gt;If humans wrote the references by hand, this would last as long as any paperwork reform. Split it: the AI creates, the human confirms, the machine verifies.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The AI finds the evidence and links it&lt;/strong&gt; — when drafting a business case, the AI searches the hub for the relevant meeting decision, budget line and OKR, and links them by ID&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The human confirms once&lt;/strong&gt; — "is this meeting, this decision, the right basis?" Reading free-form text fluctuates, so a human looks here, once&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The machine verifies from then on&lt;/strong&gt; — existence, status, values, period and authority of the linked IDs, at every later step: when the case goes for approval, when the PO is cut, when the invoice arrives&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;History needs the same care. Pulling "the agreed price" out of an email thread or a call transcript is AI work and fluctuates. So the extracted agreement is attached to the request as a field and a human confirms it once; after that, matching is field-to-field, by the machine. Same direction as the start of this section: don't re-read free text every time; turn it into a field once and make the field the truth.&lt;/p&gt;

&lt;p&gt;Expenses are where this bites hardest. Who, with whom, for what purpose, how much, as fields; tied to a budget ID and an approval record; under the policy cap — all verified by the machine before a human sees it. If anything is missing, it goes back. &lt;strong&gt;Spend against a budget that was never approved stops right there.&lt;/strong&gt; The same holds for initiatives: &lt;strong&gt;a business case not tied to a decision and a current-year budget line does not go to approval.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Check the machinery, not just the output
&lt;/h3&gt;

&lt;p&gt;Checking each finished artifact is not enough. You also check that &lt;strong&gt;when a template, an AI instruction or a procedure changes, the artifacts coming out keep their properties.&lt;/strong&gt; This is the unit test and the end-to-end test of section 3.&lt;/p&gt;

&lt;p&gt;The failures are silent. Someone tidied the proposal template's layout and the assumptions section vanished. Someone shortened the AI's instructions and quotes stopped carrying a valid-until date. A field was renamed in the ERP and forecast revenue came out as zero.&lt;/p&gt;

&lt;p&gt;The fix: keep a fixed set of inputs (a handful of past deals), regenerate automatically every time the machinery changes, and run the checks above. If anything fails, the change is blocked. Nobody waits for a human to notice; it goes red the moment it changes. Software teams call this a test, and since the AI now writes the tests, the cost of adding them has fallen a long way.&lt;/p&gt;

&lt;h3&gt;
  
  
  The last step
&lt;/h3&gt;

&lt;p&gt;If the AI also renders the fields into prose, you need one more check after rendering, because rendering is where a number not in the fields gets added, or "tentative" gets rewritten as final.&lt;/p&gt;

&lt;p&gt;Draw the line here too. &lt;strong&gt;Whether every number, date and proper noun in the fields appears in the prose&lt;/strong&gt; can be matched by a machine. &lt;strong&gt;Whether the prose contains a claim that is not in the fields&lt;/strong&gt; requires reading meaning, so it stays with the AI review. If you want that second half on the machine, render with templates: the check disappears, at the cost of stiffer prose. How much to let the AI write is that trade-off.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Five scenes: inbound lead, business trip, client dinner, business case, vendor invoice
&lt;/h2&gt;

&lt;p&gt;Now the whole thing against real workflows. The flows follow common US practice. Times and counts are illustrative. The "human interventions" below extend the section 2 count to a whole workflow: every confirmation, approval, choice and execute instruction in the scene.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scene 1: From a web inquiry to a proposal in the customer's inbox
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Today&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An IT manager at a prospect fills in the website form: "Must integrate with our existing order-management system. Looking at next fiscal year's budget." The form lands in a shared inbox; marketing routes it to an SDR. Industry data says inbound leads convert far better when contacted within five minutes, so the SDR calls, qualifies on BANT (budget, authority, need, timeline), and notes the economic buyer, the integration requirement and a competitor in evaluation. The notes live in the SDR's own doc. "Warm lead, booked for Thursday," goes in Slack. The AE runs discovery, then digs old proposals out of Google Drive and drafts with an AI. The form's "must integrate" and the SDR's "economic buyer is the CIO" never reach the AE. Then the section 1 story: VP reads three times, Legal once, deal desk reviews the discount, four to five business days to send.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With a harness&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The form becomes the request record (①): company, contact, inquiry text, source&lt;/li&gt;
&lt;li&gt;The SDR's call is turned into fields by the AI after the call: budget, timeline, economic buyer, existing systems, competitors. The SDR confirms once; it attaches to the request (①⑥)&lt;/li&gt;
&lt;li&gt;The discovery call is also turned into fields and confirmed by the AE: agreed scope, concerns raised, open items (①⑥⑧)&lt;/li&gt;
&lt;li&gt;The AI produces the proposal fields, reading the inquiry text, the qualification notes, the call fields, the prospect's latest 10-K and similar past deals on its own. "Integrate with order management" lands in scope, "next fiscal year's budget" in assumptions, "CIO" as the addressee, without anyone saying so (②③)&lt;/li&gt;
&lt;li&gt;Machines check: price = quote, assumptions present, source field on the adoption-rate figure, no price marked tentative rendered as final, discount within policy or deal-desk approval attached. Failures go back to the AI (④)&lt;/li&gt;
&lt;li&gt;A second AI reads: "the integration section doesn't answer the question they asked." The first fixes. One remaining point becomes a concern (⑤)&lt;/li&gt;
&lt;li&gt;The VP's queue shows the fields and the concern. The VP reads the fields, not the prose, and approves through to "OK to send." The machine has already confirmed none of the Legal-review triggers apply (⑥)&lt;/li&gt;
&lt;li&gt;Render, run the post-render match, verify recipient and version, send (⑦). Everything is on the request record (⑧)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Humans touched it three times: the SDR's confirmation, the AE's confirmation, the VP's approval. What the prospect typed into the form reached the proposal without passing through anyone's memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scene 2: A business trip, from pre-approval to gift to expense report
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Today&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On the call: "We'll come to your HQ in Austin next week." Back at the desk, the AE files a pre-trip request in the T&amp;amp;E tool (about two-thirds of companies require one): dates, destination, purpose, estimate. The manager approves. Flights and hotel get booked by the AE. The day before: "should I bring something?" Nobody remembers what was brought last time, or whether the prospect's company even allows gifts; something gets picked up at the airport. Afterwards, a trip report, and an expense report: flights, hotel, meals against per diem, receipts for anything over $75 (policy says $25), all within 30 days. Finance checks it against policy. The hotel is over the city cap: returned. The AE writes a justification and resubmits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With a harness&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The moment "on-site visit, date" becomes a field in the deal record, the AI drafts the pre-trip request: dates, destination, purpose, estimate, the opportunity ID. The AE confirms and presses "submit" (①②③⑥)&lt;/li&gt;
&lt;li&gt;Machines check: hotel within the city cap, meals within the GSA per-diem for the destination, the opportunity's travel budget has balance (④). It lands in the manager's queue; the manager presses "approved" (⑥)&lt;/li&gt;
&lt;li&gt;Flights and hotel are booked through the travel tool's API on approval (⑦)&lt;/li&gt;
&lt;li&gt;The day before, the AI surfaces: the three attendees, names and titles. Last visit was in April; we brought a box of local pastries. The dinner record notes their VP avoids sugar. Their press release from last week. Our open items from last time (②)&lt;/li&gt;
&lt;li&gt;On the gift: the AI first checks the prospect's published gift policy on the account record. If their policy forbids gifts, or the contact is a public official where FCPA applies, it says so and proposes no gift. Otherwise it suggests two options under our gift cap, mindful that the IRS deduction limit is $25 per recipient per year, individually wrapped for the headcount, not a repeat of April, and not from a store next to their office (③). The AE picks one (⑥)&lt;/li&gt;
&lt;li&gt;On the way home, the AI drafts the trip report from the meeting fields and the expense report from the corporate-card feed and the booking records, applying per diem from policy (②③)&lt;/li&gt;
&lt;li&gt;Machines check: dates within the trip window, amounts match the card feed, receipts attached for anything at or above the policy threshold, hotel under the cap, per diem for the right city, no duplicate of a card line (④)&lt;/li&gt;
&lt;li&gt;The AE reviews the fields and presses "submit" (⑥). What reaches Finance is only the exception that needs a human reason, such as a late-night rideshare outside policy&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Humans: the AE three times (submit the request, pick the gift, submit the report), the manager once. Finance: zero, or one on an exception.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scene 3: A client dinner on the expense report
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Today&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Dinner with the prospect's VP and two of her team. The AE keeps the receipt. The following week, the expense tool: date, restaurant, amount, attendees. US tax rules require five things for a business meal: amount, time, place, business purpose, and the business relationship of everyone at the table; meals are 50% deductible, and if the evening had included a game or a show, that part would not be deductible at all. The AE reconstructs titles and company names from memory. The manager approves; Finance reviews. "Business relationship missing for one attendee": returned.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With a harness&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The dinner was scheduled from the deal record, so attendees are already fields: three from the prospect, two from us, the opportunity ID (⑧)&lt;/li&gt;
&lt;li&gt;The corporate-card transaction arrives through the API. The AI drafts the expense fields: date, restaurant and city, amount, each attendee's name, company and relationship, headcount, business purpose (the opportunity ID), per-person amount (②③)&lt;/li&gt;
&lt;li&gt;Machines check: attendees match the meeting record, all five substantiation elements present, receipt attached (over $75), per-person amount within the policy cap, no entertainment line mixed in, matches the pre-approval, no duplicate receipt, budget balance (④)&lt;/li&gt;
&lt;li&gt;If it passes, the AE presses "submit" (⑥). Finance sees only what fell outside policy&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Humans: the AE, once. "Who, with whom, for what purpose, how much" is assembled by the AI from the meeting record and the card feed and verified by the machine, not reconstructed from memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scene 4: From a business case to spending approval and the annual budget
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Today&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the quarterly planning meeting: "Let's run a partner training program next year." The owner writes a business case from scratch. Which budget line has room? Ask FP&amp;amp;A. Which company OKR does it serve? Write it in the first paragraph, from memory. The spending request goes up the delegation-of-authority chain: manager, director, VP. The VP sends it back: "That line is already committed for the year." "Didn't we try a partner program two years ago? What happened?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With a harness&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The planning meeting's notes carry the decision as a field: "Build a case for partner training. Owner: A. Due: March 15" (⑧)&lt;/li&gt;
&lt;li&gt;The AI drafts the business case fields and, as it does, surfaces the decision ID, the matching AOP line and its remaining balance, the OKR ID, and past similar initiatives with outcomes. "Partner training, two years ago, cancelled: attendance was half of plan" goes into "prior history" even though nobody remembered it (②③)&lt;/li&gt;
&lt;li&gt;The owner confirms once: "this meeting, this budget line, this OKR are the right basis" (⑥)&lt;/li&gt;
&lt;li&gt;Machines check: the decision exists and the owner matches; the budget line exists, is approved for this fiscal year, and the balance covers the ask; the OKR link is a field; the prior initiative's outcome is referenced (④)&lt;/li&gt;
&lt;li&gt;A second AI reads: "the benefit estimate doesn't state an attendance assumption." The first fixes. What remains becomes a concern (⑤)&lt;/li&gt;
&lt;li&gt;The director's queue shows the fields and the concern. The director checks the evidence links and the concern, and approves (⑥)&lt;/li&gt;
&lt;li&gt;The spending request inherits the case's fields. The machine confirms the amount matches the attached quote and, per the DoA matrix, routes it to the VP (③④)&lt;/li&gt;
&lt;li&gt;The VP decides on two things only: that decision, case, budget line and quote are linked by ID, and the concern (⑥)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Humans: the owner once (confirming the evidence), the director once, the VP once. "Is there budget?" and "didn't this fail before?" are verified by the machine when the case is drafted, not discovered by a human halfway up the approval chain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scene 5: From a vendor invoice to payment
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Today&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An invoice arrives as a PDF by email. AP matches it against the PO and the receipt: is the bill-to right, is the vendor the one we expected, do items and amounts match the PO, is the tax right, are the bank details and due date there. The PO can't be found. Has the work been accepted? Email the requester. The amount differs from the quote. Email the requester. Payment waits for replies. Meanwhile, the email thread also contains, from last month, a "we've changed our bank account" message from the vendor's domain. The FBI logged about 25,000 business email compromise complaints in 2025, with reported losses above $3 billion, and most of that money moved by wire or ACH, which is close to unrecoverable once it lands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With a harness&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The AI turns the PDF into fields: vendor, invoice number, line items, amounts, tax, bank details, due date (③)&lt;/li&gt;
&lt;li&gt;Machines check: the PO number exists and is approved; the receipt or acceptance record is complete; items and amounts match the PO; the due date matches the contract's terms; the bank details match the vendor master, and any change to them carries a callback verification to the phone number on file, never a number from the email; the amount is under the threshold for single approval, otherwise dual approval is required; no duplicate invoice number; budget balance (④)&lt;/li&gt;
&lt;li&gt;What passes lands in AP's queue; AP presses "pay" (⑥). Payment executes through the bank API and the journal entry posts (⑦⑧)&lt;/li&gt;
&lt;li&gt;What fails goes back with a reason. "Acceptance not recorded" appears in the requester's queue as "record acceptance"; the moment they do, the match re-runs (④⑨)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Humans: AP once; the requester once, on an exception. "Email the requester" becomes a status change on a queue, and the bank-change message never reaches a human who might believe it.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the five scenes share
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Humans touch the entry, the exit, and the exceptions neither machine nor AI could decide&lt;/li&gt;
&lt;li&gt;"Remember," "search" and "ask around" become references inside the hub. What humans forgot, the machine surfaces&lt;/li&gt;
&lt;li&gt;Stopping and surfacing run on the same data. The dinner's attendee list feeds both the gift suggestion and the expense check&lt;/li&gt;
&lt;li&gt;Return reasons are counted as queue states, not buried in email&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  8. Turning human judgment into a mechanism
&lt;/h2&gt;

&lt;p&gt;Some judgments stay with the AI's flags, so human evaluation never disappears. If it won't disappear, make how it happens a mechanism. Three parts.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Visibility&lt;/strong&gt; — how many items are waiting on a human, where they are stuck, in whose queue, across every document type, on one screen&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A place to decide&lt;/strong&gt; — pass, return, or pick a reason, right there. Not in a meeting or an email. Reasons are a pick-list because they need to be counted&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A path from decision to rule&lt;/strong&gt; — the same reason twice is a candidate for a rule. If it can be written as structure, consistency or prohibition, it becomes a machine check; if not, it goes into the AI's instructions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Without the third, this is just the AI review products of section 5: "it told you." The mechanism counts; nobody has to notice. New rules enter as warnings, existing violations are cleared, then the rule becomes an error.&lt;/p&gt;

&lt;p&gt;Accumulated human decisions also solve two problems. If the AI scores risk and "below 80 goes to a human," the 80 has no basis, but the distribution of passed-versus-returned items will tell you where to draw the line. And when the AI model is updated, re-run past human decisions as a regression test and detect every verdict that changed.&lt;/p&gt;

&lt;p&gt;The accumulation is not a UX nicety. It is &lt;strong&gt;the mechanism that keeps shrinking the number of items reaching humans.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Where to start
&lt;/h2&gt;

&lt;p&gt;You don't build all of it at once. The first 90 days can look like this.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Measure&lt;/strong&gt; — take ten proposals, meeting notes and contracts; count human interventions from request to send, and the decision split (machine / AI / human). Count what share of past comments the section 6 tables would have caught&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put structure, consistency and budget/approval cross-references on the machine&lt;/strong&gt; — presence of fields, matching numbers, a link to an approved budget line. Leave source validity and prose quality alone&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add the pre-send match&lt;/strong&gt; — recipient and version equal what was approved. One deterministic check just before the irreversible point&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick one surfacing target&lt;/strong&gt; — past visits, dinners and open items before a trip; or the inquiry, qualification notes and past proposals before a draft. Hand the data you check with to the AI before it drafts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make return reasons a pick-list and count them&lt;/strong&gt; — line up every reason that appeared twice as a rule candidate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connect the approval system and the e-signature tool if they have an API; replace them if they don't&lt;/strong&gt; — wherever a human carries work between stages, make a machine carry it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Three objections, answered in advance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Our existing SaaS already does this."&lt;/strong&gt; The individual features exist. What doesn't is one flow of control across document types from request to improvement. The four gaps in section 5 are connections, not features, and a product without an API cannot be on the connecting side. An API alone isn't enough either: you can surface last quarter's dinner notes before a trip only if they were recorded at that granularity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Will Legal, Finance and the auditors accept AI automation?"&lt;/strong&gt; In this design, the AI never has authority to stop. Stopping is done by deterministic machine checks, and every pass and fail is on the request record. The human final decision remains. The conditions that trigger Legal review are themselves machine-checked rules. Compared with decisions passed around in email, this is more recorded and more reproducible, which is what a SOX-style control framework asks for in the first place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Won't quality drop?"&lt;/strong&gt; Measure it by post-delivery returns, corrections and incidents. If interventions fall and those rise, move one step back up. That is why measurement goes first.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Open questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Who pays for evaluation.&lt;/strong&gt; If humans still evaluate every artifact, the bottleneck has moved, not shrunk. Visibility's job is not to be readable but to keep reducing what reaches humans; if that doesn't work, none of this holds. Reviewing fields instead of prose makes each item lighter; it does not make them fewer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Documents whose shape can't be fixed.&lt;/strong&gt; A proposal whose structure changes with every customer. How far can fields be enforced before quality suffers?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evaluators disagree.&lt;/strong&gt; Accumulating human decisions as ground truth assumes they agree. They don't. Whose verdict is the truth is unsettled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connecting existing systems.&lt;/strong&gt; Wiring the approval system, e-signature and CRM into a hub is more an operations problem than a technical one. The migration order is not decided.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Build the layer, not the app
&lt;/h2&gt;

&lt;p&gt;In software, nobody builds the section 3 machinery per project. There is shared infrastructure (GitHub, CI) and each project puts its own rules and tests on top. Business work has almost none of that shared layer, so every company writes its own prompts, wires its own approval chains and decides its own delivery steps, per workflow, from zero.&lt;/p&gt;

&lt;p&gt;Build the layer first and every workflow on top of it moves much faster. And &lt;strong&gt;the layer accumulates&lt;/strong&gt;: every workflow adds check rules, every decision adds to the human ground truth, the machine catches more, and fewer items reach humans. &lt;strong&gt;The more workflows it carries, the stronger it gets.&lt;/strong&gt; That is the difference from an ordinary tool.&lt;/p&gt;

&lt;p&gt;Requests, checks, approvals, delivery and records are needed by every business and differentiate none. That is exactly why they belong in a shared layer rather than in each product.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Author's note: this "shared layer" is one concrete piece of the &lt;a href="https://singularitysociety.org/activities/bootcamp/2025-04-compound_startup/" rel="noopener noreferrer"&gt;Compound Startup&lt;/a&gt; idea presented at SS Connect: each venture keeps only its core and pushes everything else into shared infrastructure.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Sidebar: the software vocabulary
&lt;/h2&gt;

&lt;p&gt;The tools from section 3, in business terms.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;In business terms&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Instructions file (CLAUDE.md)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Standing instructions the AI reads on every task. Kept short; long files stop being followed&lt;/td&gt;
&lt;td&gt;Custom instructions, the internal prompt library&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Skill&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A procedure the AI can invoke by name, so nobody re-explains it&lt;/td&gt;
&lt;td&gt;A saved "how we write a proposal" the AI runs on request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;lint&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Machine checks on style rules, hundreds of them; fails block progress&lt;/td&gt;
&lt;td&gt;"Does the proposal have a pricing section," checked by a machine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Type checking&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Verifies "this value is money," "this value is a date"&lt;/td&gt;
&lt;td&gt;Quantity can't be "about 10"; delivery date can't be "TBD"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Unit test&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pins "this input gives this output" per component; goes red when a change breaks it&lt;/td&gt;
&lt;td&gt;After editing the quote template, the same deal still totals the same&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;End-to-end test&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Replays a user's path from start to finish automatically&lt;/td&gt;
&lt;td&gt;Run "opportunity → quote → approval → send" on a dummy deal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Runs all the checks above on every change; nothing merges unless all pass&lt;/td&gt;
&lt;td&gt;Every machine check sits before "send to next approver"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI code review&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A second AI reads the change and comments; loops to approval, but cannot block&lt;/td&gt;
&lt;td&gt;CLM playbook review, run to approval automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Issue / merge&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One ticket per request / taking a verified change into the main line&lt;/td&gt;
&lt;td&gt;The request record / send and sign&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Appendix A: Glossary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Harness&lt;/td&gt;
&lt;td&gt;The control system that lets an AI agent deliver consistent quality with minimal human intervention: wrap probabilistic behavior in deterministic constraints and increase machine-made decisions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deterministic / probabilistic&lt;/td&gt;
&lt;td&gt;Same input, same answer, always / the answer varies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Machine / AI / human layer&lt;/td&gt;
&lt;td&gt;The three layers: stop / draft and flag / notice and decide&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ten stages&lt;/td&gt;
&lt;td&gt;Request, context, draft, machine check, AI review, human decision, send, record, visibility, improvement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hub&lt;/td&gt;
&lt;td&gt;The one place the ten stages live. In software, GitHub&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human interventions / decision split&lt;/td&gt;
&lt;td&gt;The two numbers that measure whether the harness works (section 2)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fields&lt;/td&gt;
&lt;td&gt;The set of elements a document is made of: problem, price, assumptions. Prose is their output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-reference&lt;/td&gt;
&lt;td&gt;Checking a document against history, policy, related records and the plan/budget, not only against itself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DoA matrix&lt;/td&gt;
&lt;td&gt;Delegation of authority: who may approve how much&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Three-way match&lt;/td&gt;
&lt;td&gt;Verifying PO, receipt and invoice agree before paying&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BEC&lt;/td&gt;
&lt;td&gt;Business email compromise: fraudulent payment or bank-change instructions sent while impersonating a vendor or executive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;A connection point for operating a tool from outside, without a human at a screen. The precondition for machines carrying work between stages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GUI&lt;/td&gt;
&lt;td&gt;The screen a human operates. In a harness, a viewer for final checks plus a minimal set of buttons&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Appendix B: Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Original Japanese article: &lt;a href="https://singularitysociety.org/articles/blog/2026-10-11-business-harness/" rel="noopener noreferrer"&gt;https://singularitysociety.org/articles/blog/2026-10-11-business-harness/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;What a harness is on the software side, with six implementations (Japanese): &lt;a href="https://zenn.dev/singularity/articles/agent-harness-anatomy" rel="noopener noreferrer"&gt;https://zenn.dev/singularity/articles/agent-harness-anatomy&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The talk this grew out of (Japanese slides): &lt;a href="https://github.com/isamu/presentation/tree/main/2026-09-06-ai-event" rel="noopener noreferrer"&gt;https://github.com/isamu/presentation/tree/main/2026-09-06-ai-event&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Prior art for checking documents as structured data: Schematron (ISO/IEC 19757), remark-lint, textlint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The workflows in section 7 draw on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Business meal substantiation, the $75 receipt rule, 50% deductibility and nondeductible entertainment (IRS Publication 463): &lt;a href="https://www.brex.com/spend-trends/expense-management/irs-receipt-requirements" rel="noopener noreferrer"&gt;https://www.brex.com/spend-trends/expense-management/irs-receipt-requirements&lt;/a&gt;, &lt;a href="https://ramp.com/blog/irs-receipt-requirements" rel="noopener noreferrer"&gt;https://ramp.com/blog/irs-receipt-requirements&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Procure-to-pay and three-way match: &lt;a href="https://www.doss.com/glossary/three-way-match" rel="noopener noreferrer"&gt;https://www.doss.com/glossary/three-way-match&lt;/a&gt;, &lt;a href="https://lapasar.com/procurement/procure-to-pay" rel="noopener noreferrer"&gt;https://lapasar.com/procurement/procure-to-pay&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Inbound qualification, BANT / MEDDIC, five-minute response: &lt;a href="https://leadfeeder.com/blog/marketing-strategy/b2b-sales-process" rel="noopener noreferrer"&gt;https://leadfeeder.com/blog/marketing-strategy/b2b-sales-process&lt;/a&gt;, &lt;a href="https://blog.hubspot.com/sales/a-step-by-step-guide-to-the-meddic-sales-qualification-process" rel="noopener noreferrer"&gt;https://blog.hubspot.com/sales/a-step-by-step-guide-to-the-meddic-sales-qualification-process&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Travel policy, pre-trip approval, GSA per diem, expense platforms: &lt;a href="https://www.ramp.com/blog/corporate-travel-policy" rel="noopener noreferrer"&gt;https://www.ramp.com/blog/corporate-travel-policy&lt;/a&gt;, &lt;a href="https://ramp.com/blog/expensify-vs-concur-vs-ramp" rel="noopener noreferrer"&gt;https://ramp.com/blog/expensify-vs-concur-vs-ramp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Business gifts, the $25 deduction limit, FCPA: &lt;a href="https://blog.giftpack.ai/article/en-corporate-gifts-united-states-tax-fcpa-privacy-import-controls" rel="noopener noreferrer"&gt;https://blog.giftpack.ai/article/en-corporate-gifts-united-states-tax-fcpa-privacy-import-controls&lt;/a&gt;, &lt;a href="https://nolo.com/legal-encyclopedia/are-gifts-business-clients-deductible.html" rel="noopener noreferrer"&gt;https://nolo.com/legal-encyclopedia/are-gifts-business-clients-deductible.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Annual operating plan and budget approval: &lt;a href="https://corporatefinanceinstitute.com/resources/fpa/budgeting-process-guide-fpa" rel="noopener noreferrer"&gt;https://corporatefinanceinstitute.com/resources/fpa/budgeting-process-guide-fpa&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Business email compromise, 2025 FBI IC3 figures, callback and dual-approval controls: &lt;a href="https://www.eftsure.com/statistics/payment-fraud-index/" rel="noopener noreferrer"&gt;https://www.eftsure.com/statistics/payment-fraud-index/&lt;/a&gt;, &lt;a href="https://beancount.io/blog/2026/08/13/business-email-compromise-3-billion-fbi-payment-verification-controls-guide" rel="noopener noreferrer"&gt;https://beancount.io/blog/2026/08/13/business-email-compromise-3-billion-fbi-payment-verification-controls-guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AI contract review playbooks: &lt;a href="https://ironcladapp.com/product/review-contracts" rel="noopener noreferrer"&gt;https://ironcladapp.com/product/review-contracts&lt;/a&gt;, &lt;a href="https://doc.workday.com/evisort/en-us/contract-management-and-document-intelligence/workflow/workflow-tickets/review-documents-with-ai-playbooks.html" rel="noopener noreferrer"&gt;https://doc.workday.com/evisort/en-us/contract-management-and-document-intelligence/workflow/workflow-tickets/review-documents-with-ai-playbooks.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>automation</category>
      <category>architecture</category>
    </item>
    <item>
      <title>ever-better: 13 commands that make a codebase only able to get better</title>
      <dc:creator>Isamu Arimoto</dc:creator>
      <pubDate>Sat, 15 Aug 2026 09:43:57 +0000</pubDate>
      <link>https://dev.to/isamu/ever-better-13-commands-that-make-a-codebase-only-able-to-get-better-35d0</link>
      <guid>https://dev.to/isamu/ever-better-13-commands-that-make-a-codebase-only-able-to-get-better-35d0</guid>
      <description>&lt;p&gt;Adding a strict linter to an existing repository produces four thousand errors and gets reverted. The usual workaround — set everything to &lt;code&gt;warn&lt;/code&gt; — enforces nothing, and the count grows quietly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/isamu/ever-better" rel="noopener noreferrer"&gt;ever-better&lt;/a&gt; is the way around that. It records every violation that exists today as a &lt;strong&gt;ceiling&lt;/strong&gt;: old code is grandfathered, new code is held to the whole rule set, and the ceiling can fall but never rise.&lt;/p&gt;

&lt;p&gt;Below is every command, what it does, and the problem it exists for.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx ever-better &amp;lt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MIT, zero runtime dependencies, Node 20.11+.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;diagnose&lt;/code&gt; — what is missing, and what each gap costs
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ever-better diagnose            &lt;span class="c"&gt;# read-only&lt;/span&gt;
ever-better diagnose &lt;span class="nt"&gt;--write&lt;/span&gt;    &lt;span class="c"&gt;# also writes QUALITY.md&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What it does.&lt;/strong&gt; Surveys the repository — package manager, language, framework, runtime, ESLint setup, formatter, test runner, dead-code and duplication scans, agent instructions, CI, file sizes — and lists the gaps with the phase each belongs to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why.&lt;/strong&gt; "Add linting" is not a task; it is six of them in an order that matters. Formatting has to land before linting or the first cleanup PR is a diff nobody can read. Type-aware rules cannot run without TypeScript. This prints that order instead of leaving you to discover it.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;bootstrap&lt;/code&gt; — install the tooling, generate the configs
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ever-better bootstrap
ever-better bootstrap &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What it does.&lt;/strong&gt; Adds the dependencies, writes &lt;code&gt;eslint.config.mjs&lt;/code&gt;, Prettier config, &lt;code&gt;.gitattributes&lt;/code&gt;, &lt;code&gt;knip.json&lt;/code&gt;, the package scripts CI needs, workflows for three platforms, &lt;code&gt;dependabot.yml&lt;/code&gt;, and a secret-scan workflow. Prices each TypeScript strictness flag by enabling it in a temporary config and counting the errors — turning on the free ones, reporting what the rest would cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why.&lt;/strong&gt; Every rule it writes is an &lt;strong&gt;error&lt;/strong&gt;, not a warning. That is only survivable because of the next command.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;freeze&lt;/code&gt; — pin today's violations as the ceiling
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ever-better freeze
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What it does.&lt;/strong&gt; Runs &lt;code&gt;eslint --suppress-all&lt;/code&gt;, which records how many violations each rule has in each file, then stores the totals in &lt;code&gt;.ever-better/state.json&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Baseline pinned: 4,942 violations across 72 rules are now grandfathered.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why.&lt;/strong&gt; From this commit on, the 4,942 are silent and violation 4,943 fails the build. You do not have to fix anything today to start enforcing everything tomorrow.&lt;/p&gt;

&lt;p&gt;The ratchet itself is ESLint's own bulk-suppressions feature. ever-better is the part around it: knowing which rules to add, keeping the ledger, and failing CI when a number goes up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It refuses to run twice.&lt;/strong&gt; A second freeze would grandfather everything added since — which is the one thing the baseline exists to prevent.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;check&lt;/code&gt; — the gate
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ever-better check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What it does.&lt;/strong&gt; Compares current counts against the ceiling and exits non-zero if anything rose.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;8 unsuppressed error(s) — these are new since the baseline:
  2  id-length
  1  prettier/prettier
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why.&lt;/strong&gt; A baseline is only a ratchet if something rejects a regression. Without CI it is a note.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do not pipe it. &lt;code&gt;ever-better check | tail&lt;/code&gt; exits with &lt;code&gt;tail&lt;/code&gt;'s status and reports a failing gate as success.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;prune&lt;/code&gt; — lower the ceiling after a fix
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ever-better prune
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What it does.&lt;/strong&gt; Removes suppressions for violations that no longer exist, so the recorded ceiling matches reality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why.&lt;/strong&gt; Without it, fixing 100 violations changes nothing: the ceiling still permits them, and they can come back. This is what makes the number monotonic.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;next&lt;/code&gt; — what to drain first
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ever-better next
ever-better next &lt;span class="nt"&gt;--fan-in&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What it does.&lt;/strong&gt; Ranks the backlog by &lt;strong&gt;which edit enforces the most&lt;/strong&gt;, not by which rule is biggest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;take these first — one or two edits, and the rule is enforced in that file for good:
    1  src/browser.js  @typescript-eslint/no-require-imports
    1  src/common.js   no-param-reassign
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why.&lt;/strong&gt; The ratchet is per file per rule. A file with no suppression left for a rule fails on its next violation, whatever that rule's total is elsewhere — so two violations in one file buy more enforcement than twenty spread across twenty files.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--fan-in&lt;/code&gt; adds how many files import each one, because a fix in a widely imported module lands errors in files the diff never opened.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;report&lt;/code&gt; — what the debt actually looks like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ever-better report
ever-better report &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What it does.&lt;/strong&gt; Prints the backlog as a &lt;strong&gt;rule × area&lt;/strong&gt; table, and appends it to &lt;code&gt;$GITHUB_STEP_SUMMARY&lt;/code&gt; when that is set.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why.&lt;/strong&gt; &lt;code&gt;next&lt;/code&gt; answers "which edit enforces the most". This answers "what shape is this repository's debt". And because the generated workflow runs it after &lt;code&gt;check&lt;/code&gt; with &lt;code&gt;if: always()&lt;/code&gt;, &lt;strong&gt;the run where the gate just failed is the run where the backlog is visible&lt;/strong&gt; — no one has to edit a workflow to get a report.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;secrets&lt;/code&gt; — scan the history for committed credentials
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ever-better secrets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What it does.&lt;/strong&gt; Runs &lt;code&gt;gitleaks&lt;/code&gt; over &lt;strong&gt;both&lt;/strong&gt; the history and the working tree, failing on any finding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why both.&lt;/strong&gt; Either alone passes a repository that is holding a secret. A history scan misses the key you pasted an hour ago and have not committed. A working-tree scan misses the key that was committed and then deleted — which is still in every clone.&lt;/p&gt;

&lt;p&gt;This is the one check with no baseline. A leaked key is not debt you ratchet down.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;migrate&lt;/code&gt; — JavaScript to TypeScript
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ever-better migrate            &lt;span class="c"&gt;# show the plan&lt;/span&gt;
ever-better migrate &lt;span class="nt"&gt;--all&lt;/span&gt;      &lt;span class="c"&gt;# rename everything in one pass&lt;/span&gt;
ever-better migrate &lt;span class="nt"&gt;--file&lt;/span&gt; src/foo.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What it does.&lt;/strong&gt; Renames with &lt;code&gt;git mv&lt;/code&gt; so history follows, and lets the lint fallout land in the ratchet instead of blocking the migration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why.&lt;/strong&gt; Types are the cheapest rule set there is, and the tier that finds the most real bugs cannot run without them. On a 13-year-old JavaScript project, renaming 49 files and changing no logic produced &lt;strong&gt;2,641 type errors&lt;/strong&gt; — every one of them a question nobody had answered.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;emit-diff&lt;/code&gt; — prove a refactor changed nothing
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ever-better emit-diff &lt;span class="nt"&gt;--against&lt;/span&gt; main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What it does.&lt;/strong&gt; Compiles the working tree and a git ref, then compares the emitted JavaScript.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why.&lt;/strong&gt; Types erase at compile time, so a change that only moves types must produce &lt;strong&gt;byte-identical output&lt;/strong&gt;. That is a proof, in seconds. No amount of test coverage states it as strongly, because tests only cover the paths someone thought of.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;catalog&lt;/code&gt; — the helpers that already exist
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ever-better catalog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What it does.&lt;/strong&gt; Writes &lt;code&gt;docs/shared-helpers.md&lt;/code&gt;: every exported function, grouped by directory, with the first sentence of its doc comment. Point your CLAUDE.md at it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why.&lt;/strong&gt; A linter sees inside one file. Duplication detection only notices copies once they are textually similar, and two independent implementations of the same idea rarely are. &lt;strong&gt;Nothing else reports the same function written a sixth time under a sixth name.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;status&lt;/code&gt; — where you are
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ever-better status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What it does.&lt;/strong&gt; Prints the phase, the frozen date, the current backlog, which rules improved or regressed, and the smallest remaining backlogs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;STALE      100 commits since the diagnosis; re-run diagnose before trusting it
phase      drain
backlog    3532
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why.&lt;/strong&gt; That &lt;code&gt;STALE&lt;/code&gt; line matters more than it looks. A diagnosis from a hundred commits ago is a description of a repository that no longer exists.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;log&lt;/code&gt; — why, not how many
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ever-better log &lt;span class="nt"&gt;--kind&lt;/span&gt; drained  &lt;span class="nt"&gt;--rule&lt;/span&gt; max-depth &lt;span class="s2"&gt;"6 violations, 1 real bug"&lt;/span&gt;
ever-better log &lt;span class="nt"&gt;--kind&lt;/span&gt; deferred &lt;span class="nt"&gt;--rule&lt;/span&gt; max-lines &lt;span class="s2"&gt;"router.ts is 1400 lines; its own project"&lt;/span&gt;
ever-better log &lt;span class="nt"&gt;--kind&lt;/span&gt; issue    &lt;span class="nt"&gt;--rule&lt;/span&gt; no-floating-promises &lt;span class="s2"&gt;"opened #42 — product decision"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What it does.&lt;/strong&gt; Records what happened against the current commit — the only command that writes the &lt;strong&gt;Work log&lt;/strong&gt; in &lt;code&gt;QUALITY.md&lt;/code&gt;. Everything else records counts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why.&lt;/strong&gt; &lt;code&gt;deferred&lt;/code&gt; is the one that earns its keep. It renders as a &lt;strong&gt;Carried over&lt;/strong&gt; checklist stamped with the commit it was seen at, because "router.ts needs splitting" is useless four hundred commits later unless a reader can tell when it was true.&lt;/p&gt;




&lt;h2&gt;
  
  
  The whole loop, in five commands
&lt;/h2&gt;

&lt;p&gt;The other eight are for when you need them. This is the shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx ever-better diagnose     &lt;span class="c"&gt;# what is missing&lt;/span&gt;
npx ever-better bootstrap    &lt;span class="c"&gt;# install it&lt;/span&gt;
npx ever-better freeze       &lt;span class="c"&gt;# pin today's count&lt;/span&gt;
npx ever-better check        &lt;span class="c"&gt;# gate CI on it&lt;/span&gt;
npx ever-better prune        &lt;span class="c"&gt;# lower the ceiling as you fix&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Or hand the repository to an agent
&lt;/h2&gt;

&lt;p&gt;The commands are the deterministic half — count, record, gate, refuse. The judgement half is eight Claude Code skills that ship with it: which violation is a real bug, when to stop and ask, what deserves an issue rather than a fix.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/plugin marketplace add isamu/ever-better
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;ever-better
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then say &lt;code&gt;clean this repo up&lt;/code&gt;. It formats, installs, freezes, and works the backlog down one rule per pull request — fixing violations, extracting the pure functions that make the fixes testable, writing the tests, and lowering the ceiling as it goes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It is not unattended.&lt;/strong&gt; Anything it can decide from the code, it decides. Anything it cannot — ambiguous behaviour, a public API change, a refactor that is its own project — becomes an issue with the options written out, and it moves on.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it on something you did not write
&lt;/h2&gt;

&lt;p&gt;That is the honest test. Pick a repository you have never read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &lt;span class="nt"&gt;--depth&lt;/span&gt; 1 https://github.com/debug-js/debug.git
&lt;span class="nb"&gt;cd &lt;/span&gt;debug
npx ever-better diagnose
npx ever-better bootstrap
npx prettier &lt;span class="nt"&gt;--write&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
npx ever-better freeze
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add a file with a violation and run &lt;code&gt;npx ever-better check&lt;/code&gt;. The 72 existing violations stay silent; the new ones fail.&lt;/p&gt;

&lt;p&gt;On this particular repository you will also see five &lt;code&gt;(parse error)&lt;/code&gt; entries — &lt;code&gt;/* eslint-env */&lt;/code&gt;&lt;br&gt;
comments and stale &lt;code&gt;eslint-disable&lt;/code&gt; directives that flat config rejects. Those are real, they have&lt;br&gt;
no rule id, and &lt;code&gt;--suppress-all&lt;/code&gt; cannot record them, so &lt;code&gt;freeze&lt;/code&gt; names the file and line for each.&lt;br&gt;
It is a fair first impression of what the tool does: it tells you where, and it does not pretend the&lt;br&gt;
number is smaller than it is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Results welcome&lt;/strong&gt; — &lt;a href="https://github.com/isamu/ever-better/blob/main/docs/RESULTS.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/RESULTS.md&lt;/code&gt;&lt;/a&gt; collects what people froze and how much of it turned out to be real defects. It has two rows and both are mine, which is enough to notice a pattern and nowhere near enough to claim one. A run that found &lt;strong&gt;zero&lt;/strong&gt; real bugs is the most useful row that table could get.&lt;/p&gt;

&lt;p&gt;→ &lt;strong&gt;&lt;a href="https://github.com/isamu/ever-better" rel="noopener noreferrer"&gt;github.com/isamu/ever-better&lt;/a&gt;&lt;/strong&gt; (MIT)&lt;/p&gt;

</description>
      <category>eslint</category>
      <category>typescript</category>
      <category>javascript</category>
      <category>devops</category>
    </item>
    <item>
      <title>Handing an AI a big spec and letting it build the whole thing is like buying a lottery ticket</title>
      <dc:creator>Isamu Arimoto</dc:creator>
      <pubDate>Fri, 07 Aug 2026 07:27:28 +0000</pubDate>
      <link>https://dev.to/isamu/handing-an-ai-a-big-spec-and-letting-it-build-the-whole-thing-is-like-buying-a-lottery-ticket-380o</link>
      <guid>https://dev.to/isamu/handing-an-ai-a-big-spec-and-letting-it-build-the-whole-thing-is-like-buying-a-lottery-ticket-380o</guid>
      <description>&lt;p&gt;Ever since I wrote &lt;a href="https://dev.to/isamu/i-stopped-reviewing-my-own-code-heres-what-had-to-be-true-first-4nh0"&gt;I stopped reviewing my own code&lt;/a&gt;, I keep getting the same question.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I ended up with a huge pile of code and I can't review it. What should I do?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The comments say the same thing in different words. &lt;strong&gt;The volume the AI writes went up. The reading side did not keep up.&lt;/strong&gt; So what now?&lt;/p&gt;

&lt;p&gt;Here is the blunt answer first.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Don't get into that state.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Taking something that already exists and rescuing it with review alone is, I think, very hard. &lt;strong&gt;You can split it, scope it down, throw parts away — but none of those is a substitute for actually reading it.&lt;/strong&gt; And "just have the AI review it" probably isn't the answer either.&lt;/p&gt;

&lt;p&gt;And &lt;strong&gt;this is not only a team problem.&lt;/strong&gt; It happens exactly the same way when you work alone. &lt;strong&gt;The cause isn't headcount. It's writing a big spec up front and having the whole thing built in one go.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is like &lt;strong&gt;buying a single lottery ticket where you don't find out whether you won until the very end.&lt;/strong&gt; This post is about why, with arithmetic and with measurements.&lt;/p&gt;

&lt;p&gt;The second half spills over into a related question: &lt;strong&gt;how many people should be on a product.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; the numbers here are measured from my own repository. But treat commit and PR counts as &lt;strong&gt;observational data about how I work, not as evidence of productivity&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why you end up with "too big to review"
&lt;/h2&gt;

&lt;p&gt;Most of the time, I think it's because &lt;strong&gt;the whole spec was decided first and built in one shot.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You write the spec. You hand it to the AI. A large amount of code comes out. &lt;strong&gt;And now, to judge whether any of it is right, you have to read all of it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But there's a prior question.&lt;/p&gt;

&lt;h3&gt;
  
  
  You can't write a spec for something when you don't know if it's possible or if you even want it
&lt;/h3&gt;

&lt;p&gt;The starting point for &lt;a href="https://github.com/receptron/mulmoterminal" rel="noopener noreferrer"&gt;MulmoTerminal&lt;/a&gt;, which I build, was &lt;strong&gt;an experiment: can a terminal run inside a browser?&lt;/strong&gt; It worked, so I widened it into a grid. After that I just used it and added what I wanted. That's all.&lt;/p&gt;

&lt;p&gt;There was no room for a spec at that moment.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;I didn't know if it was possible&lt;/strong&gt; — nine real terminals side by side in a browser tab, usable in practice?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I didn't know if I wanted it&lt;/strong&gt; — would putting them side by side actually be nice? I couldn't tell.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;With those two unknown, what exactly is there to write?&lt;/strong&gt; Anything you write is a wish, not a spec.&lt;/p&gt;

&lt;p&gt;There are, clearly, cases where a spec works well. Building an Amazon clone. Building an internal system where the UI doesn't matter much. &lt;strong&gt;The reason a spec is writable there is that the thing already exists in the world.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A spec is a compression of knowledge that already exists. It is not a way to discover what doesn't exist yet.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You cannot compress something that isn't there to compress.&lt;/p&gt;

&lt;h3&gt;
  
  
  It's like buying a single lottery ticket
&lt;/h3&gt;

&lt;p&gt;Deciding everything before you know anything is, to me, &lt;strong&gt;betting the outcome on one ticket.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And it's worse than a lottery in one specific way.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A lottery tells you whether you won on the spot.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;A big spec doesn't tell you until you've finished building and started using it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Let's compute the odds that "all of it works"
&lt;/h3&gt;

&lt;p&gt;Hand over a big spec and let it build. &lt;strong&gt;Some of it comes out good, some of it doesn't. You can specify the UI in detail and sometimes get exactly that, sometimes not.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So what are the odds that &lt;strong&gt;all of it&lt;/strong&gt; comes out the way you wanted?&lt;/p&gt;

&lt;p&gt;Say each individual decision has a &lt;strong&gt;90% chance&lt;/strong&gt; of landing the way you intended. That's a generous assumption.&lt;/p&gt;

&lt;p&gt;(This is a simple calculation that assumes the items don't affect each other. In reality they interact, so don't take it literally — &lt;strong&gt;it's for the order of magnitude.&lt;/strong&gt;)&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Number of decisions&lt;/th&gt;
&lt;th&gt;Chance all of them land&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;73%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;59%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;35%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.5%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;It's a product, so it falls much faster than people expect.&lt;/strong&gt; Even at a near-ideal 95% per item, twenty items gives you 36%.&lt;/p&gt;

&lt;p&gt;And &lt;strong&gt;a big spec holds twenty or fifty decisions without trying.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's the part that matters. At twenty items, &lt;strong&gt;the chance that at least one of them does not land is about 88%.&lt;/strong&gt; &lt;strong&gt;So what happened to that "at least one"?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Usually it gets accepted with "&lt;strong&gt;well, it runs, good enough&lt;/strong&gt;." &lt;strong&gt;Because nobody checked which parts didn't land.&lt;/strong&gt; Checking would mean reading all of it, and the amount that came out is exactly the amount you can't read.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Are you actually satisfied with that?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  "Just write a more detailed spec" ends at implementation
&lt;/h3&gt;

&lt;p&gt;Some of you are thinking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Then write the spec in enough detail that it can't miss?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Correct.&lt;/strong&gt; The only way to raise the per-item odds is to remove ambiguity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But writing it out in full means enumerating, in advance, every decision you would otherwise make while implementing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Where the button goes, what happens when you press it, what shows up when it fails, what to do when the value is empty. &lt;strong&gt;Write all of that and yes, you get closer to what you wanted — but the volume you write, and the volume you think, moves toward implementation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And there is one decisive difference.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A spec doesn't run.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;No matter how detailed it is, it will not tell you whether it's right.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Detailing a spec looks like raising your odds. In practice it tends to be "postponing the answer while increasing the bet."&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  And AI flipped this trade-off
&lt;/h3&gt;

&lt;p&gt;"Settle it on paper first" &lt;strong&gt;used to be the correct call.&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;Before   rewriting is expensive  → building big and then fixing costs a lot
                                 → cheaper to be wrong on paper

Now      rewriting is cheap      → rebuilding doesn't cost much
                                 → cheaper to be wrong in working code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Being wrong on paper was rational because rewriting was expensive.&lt;/strong&gt; That premise moved.&lt;/p&gt;

&lt;p&gt;That said, &lt;strong&gt;not everything got cheap.&lt;/strong&gt; What got cheap is &lt;strong&gt;transcribing something whose shape you can already see.&lt;/strong&gt; Migrations, public APIs, anything wired to another company — rewriting those is still expensive. &lt;strong&gt;Settle those up front.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So: &lt;strong&gt;once you've reached the point of "let me spec this out in detail," you may as well build it there.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Give the AI instructions in small increments and &lt;strong&gt;you get something running in about the time it takes to write the detailed spec.&lt;/strong&gt; And the running version has a decisive advantage.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A spec stays silent when it's wrong.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Something that runs tells you immediately.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you're spending the time either way, &lt;strong&gt;spend it on the one that tells you.&lt;/strong&gt; That's the whole argument.&lt;/p&gt;

&lt;h3&gt;
  
  
  So how do you build? Tighten the implementation, not the spec
&lt;/h3&gt;

&lt;p&gt;First, look at what happens &lt;strong&gt;when you build big in one go and it turns out to be wrong.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Everything produced up to that point &lt;strong&gt;sits on top of that spec.&lt;/strong&gt; You aren't fixing a part; you're restacking. &lt;strong&gt;And you usually notice "restacking would have been faster" only after you've finished stacking.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So keep it small enough to throw away.&lt;/strong&gt; There's one thing to do.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Cut down to the smallest unit that needs a judgement, implement them one at a time, and judge each time.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;What you tighten is the implementation, not the spec.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On odds, it's just the table read backwards: &lt;strong&gt;decide five at a time and you're at 59%; three and you're at 73%.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But odds aren't the only thing that improves. &lt;strong&gt;Cutting small changes three other things.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The judgement itself gets more accurate&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Looking at twenty things at once and asking "is this fine?" versus looking at them one at a time — &lt;strong&gt;the second is more accurate.&lt;/strong&gt; Detail drops out when you look at everything together. And as above, &lt;strong&gt;you end up waving it through with "well, it runs."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. You can change direction partway&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you realise at the third one that the whole direction is wrong, &lt;strong&gt;you can turn there.&lt;/strong&gt; Realise it after finishing all twenty and the cost of turning is &lt;strong&gt;everything you already stacked.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. You can put it in front of people early&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This might be the biggest one. &lt;strong&gt;Getting something running early also means getting it used early.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Later in this post I describe &lt;strong&gt;how the purpose I built the thing for turned out to be wrong.&lt;/strong&gt; The only reason I found that out is that &lt;strong&gt;people could actually touch something running.&lt;/strong&gt; Had I shown nobody until it was all finished, &lt;strong&gt;it would have been completed against the wrong purpose.&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  "Isn't one at a time slow?"
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Yes.&lt;/strong&gt; Done serially, of course it is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So do it in parallel.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run about ten independent things at the same time.&lt;/strong&gt; Then &lt;strong&gt;evaluate them as they land and feed corrections back.&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;cut small, serially     →  slow. which makes you want to batch it up
cut small, in parallel  →  not slow. it can stay small
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Cutting small only becomes practical once you can run things in parallel.&lt;/strong&gt; Conversely, &lt;strong&gt;in an environment where you can't run in parallel, betting big is all that's left.&lt;/strong&gt; That, I suspect, is why "write the whole spec first" still looks rational.&lt;/p&gt;

&lt;p&gt;And what happens when you do run in parallel? &lt;strong&gt;You end up evaluating finished work, one after another.&lt;/strong&gt; When that clogs, everything stops. &lt;strong&gt;If there's one person evaluating, their spare attention is the ceiling.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That's why the second half of this post ends up being about headcount and parallelism.&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  That said, cutting small isn't automatically safe
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;It isn't as simple as "cut it smaller and per-item accuracy is preserved."&lt;/strong&gt; Cut in the wrong place and new mistakes appear at the seams.&lt;/p&gt;

&lt;p&gt;What is decisively different is this: &lt;strong&gt;you find out you were wrong sooner, and you can throw away only what you built so far.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every number in the second half of this post exists to serve that.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  This isn't AI's fault. It was always like this
&lt;/h2&gt;

&lt;p&gt;Something worth remembering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Back when only humans wrote code, specs were wrong too.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You write the spec, hand it to engineering, and it comes back: "this doesn't work as written." You touch something running and only then realize "no, not this." &lt;strong&gt;Those round trips did not happen because implementation was slow.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They happened because the spec was wrong and nobody could tell until they touched it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What AI made fast is &lt;strong&gt;mostly implementation, and the planning around it.&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;what was slow       implementation  → got fast
what was wrong      the spec        → barely changed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The cause of failure is largely unaddressed.&lt;/strong&gt; If anything, &lt;strong&gt;wrong specs now become real faster and in larger volume.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The same thing happens under Agile
&lt;/h3&gt;

&lt;p&gt;"That's why we do Agile" is a fair objection. Short cycles, build, check, correct. The distance really does shrink.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;it stops when the product owner and the team are separated.&lt;/strong&gt; The person deciding is in a meeting, not at a keyboard. Decisions lag, and in the worst case &lt;strong&gt;the kind of judgement you only discover while writing code never reaches anyone.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I don't think the real variable is waterfall vs. Agile.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The distance between the person deciding and the code.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spec everything first   the decider is farthest from the code, and decides once, from there
Agile (separated)       the decider is in a meeting; decisions come at sprint granularity
working alongside       the decider is at the keyboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agile shortened the distance. It didn't take it to zero. &lt;strong&gt;And there are decisions that only reach you at zero.&lt;/strong&gt; The examples below are what I mean.&lt;/p&gt;




&lt;h2&gt;
  
  
  The tool got smarter. You didn't
&lt;/h2&gt;

&lt;p&gt;Some of you are thinking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;But AI helps write the spec now. Isn't that different from before?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I think this is the biggest misunderstanding around.&lt;/p&gt;

&lt;p&gt;AI did get smarter. Throw it requirements and a plausible-looking spec comes back. &lt;strong&gt;And that makes it feel like your own ability went up.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It didn't.&lt;/strong&gt; The tool got smarter. &lt;strong&gt;You are still the bottleneck.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Leverage is a multiplier, not the number being multiplied
&lt;/h3&gt;

&lt;p&gt;AI is powerful leverage. &lt;strong&gt;People with a clear vision of what they want to build use it astonishingly well.&lt;/strong&gt; That part is real.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;leverage is multiplication, and the thing being multiplied hasn't changed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A plausible spec appears instantly. &lt;strong&gt;But reading it and deciding "this is sound" or "this is wrong" is still you.&lt;/strong&gt; And &lt;strong&gt;the ability to produce a good spec is still yours to have or not have.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Writing makes this easy to see.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Plausible prose is easy to get out of an AI.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Getting genuinely excellent prose out of one is still extremely hard.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Anyone who writes knows this in their gut. &lt;strong&gt;Specs and design are exactly the same.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Former CTOs joining AI labs as individual contributors is probably the same story
&lt;/h3&gt;

&lt;p&gt;This is actually happening.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Peter Bailis&lt;/strong&gt; — CTO of Workday (from May 2025). Left in March 2026 and joined Anthropic as a &lt;strong&gt;Member of Technical Staff&lt;/strong&gt;, working on reinforcement learning engineering (&lt;a href="https://thenextweb.com/news/workday-cto-bailis-anthropic-member-technical-staff" rel="noopener noreferrer"&gt;The Next Web, 2026-04-09&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bryan McCann&lt;/strong&gt; — co-founder and CTO of You.com. Also moved to Anthropic as a Member of Technical Staff (&lt;a href="https://www.theinformation.com/briefings/ai-startup-com-appoints-new-cto-co-founder-joins-anthropic" rel="noopener noreferrer"&gt;The Information&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;CTOs of public companies and fast-growing startups, dropping the title for an IC seat.&lt;/strong&gt; At minimum, several moves in the same direction have been reported recently.&lt;/p&gt;

&lt;p&gt;The Next Web characterises it this way:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;executives at the top of established technology companies choosing proximity to cutting-edge research over the authority that comes with managing large organisations&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;That is where the reporting ends.&lt;/strong&gt; Both moves are documented. &lt;strong&gt;Why they did it is not in those articles.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From here on, this is my speculation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the goal were a bigger title or better terms, being CTO somewhere else was available. Not taking that reads, to me, like a statement about priorities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Until now, building something large required a large team.&lt;/strong&gt; For a CTO-level person to realise an idea, they needed capable people and an organisation. &lt;strong&gt;AI is filling that part.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If so, &lt;strong&gt;taking an IC seat where the smartest model and unlimited access to it live, instead of leading an organisation&lt;/strong&gt;, is an entirely reasonable choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is also a story about people with a clear vision being able to move with fewer people&lt;/strong&gt; — the same direction as this post's conclusion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But again.&lt;/strong&gt; What changed is leverage.&lt;/p&gt;

&lt;p&gt;AI increases the material for a decision. It lists options, it catches omissions. &lt;strong&gt;But the criteria for picking among those options exist only inside you.&lt;/strong&gt; More material without criteria means &lt;strong&gt;you feel like you can choose, and you can't.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without something to multiply, a multiplier means nothing.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Some decisions don't exist until you build
&lt;/h2&gt;

&lt;p&gt;Abstractions don't land, so here are two real ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 1: the purpose I built it for was wrong
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/receptron/mulmoterminal" rel="noopener noreferrer"&gt;MulmoTerminal&lt;/a&gt;, which I build, is &lt;strong&gt;a tool for running many AI agents side by side.&lt;/strong&gt; The screen is a grid of cells, each running its own work.&lt;/p&gt;

&lt;p&gt;My assumption while building was clear.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;This raises the ceiling on how many you can run in parallel.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If I were writing a spec, I would have written exactly that. &lt;strong&gt;It's why I built it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once people were actually using it, I asked four of them. How many do you actually run? What's good about it?&lt;/p&gt;

&lt;p&gt;Here's what came back.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agents actually run in parallel                1 to 6
people who said "I can run more now"           0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Not one.&lt;/strong&gt; What all of them said was something else entirely.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;I can handle the same number more easily.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;One person runs one to three and dropped VS Code entirely.&lt;/strong&gt; The count didn't go up. They switched anyway.&lt;/p&gt;

&lt;p&gt;So &lt;strong&gt;the value of this tool was not "raising the ceiling." It was "not losing track."&lt;/strong&gt; The person who built it had it wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  And then it got worse
&lt;/h3&gt;

&lt;p&gt;From the same conversations:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I got up to eight, but &lt;strong&gt;the notifications became a storm and I got tired and stopped&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Notifications exist to let you run more.&lt;/strong&gt; So you can walk away and still be called back.&lt;/p&gt;

&lt;p&gt;Those notifications &lt;strong&gt;were creating a new ceiling.&lt;/strong&gt; One part of the tool was lowering the ceiling another part had raised.&lt;/p&gt;

&lt;p&gt;The cause was concrete: "finished" and "waiting on you" &lt;strong&gt;played the same sound.&lt;/strong&gt; Indistinguishable, so you end up caring about all of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There is no way to know this before building.&lt;/strong&gt; At the moment you add notifications, you assume they only push in one direction. &lt;strong&gt;Nobody finds out until someone actually runs eight and gets tired.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 2: the AI said "don't do it" — and it was wrong
&lt;/h3&gt;

&lt;p&gt;There are tools that find duplicated code automatically. At one point ours reported a lot of "the same shape appears in many places."&lt;/p&gt;

&lt;p&gt;The shape was something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;read A → error if missing → catch the failure and log it
read B → error if missing → catch the failure and log it
read C → error if missing → catch the failure and log it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I went to consolidate it, &lt;strong&gt;the AI told me not to.&lt;/strong&gt; From the dev log:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The checker reports many duplicates, but this is a structural repetition — read, error if not found, catch the exception — and &lt;strong&gt;each site is readable precisely because it is self-contained&lt;/strong&gt;. Consolidating would mean rewriting large parts of a 2,700-line file, and &lt;strong&gt;readability would get worse, not better&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The reasoning holds together.&lt;/strong&gt; "A wrong abstraction is worse than duplication" is in fact a principle we've written down. I read it and thought, fair enough.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But when I opened the code myself later, it was wrong.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What had been flagged was not a coincidental resemblance. It was &lt;strong&gt;genuinely the same thing written over and over.&lt;/strong&gt; Consolidating it made things &lt;strong&gt;noticeably cleaner.&lt;/strong&gt; We ended up doing it as a separate task.&lt;/p&gt;

&lt;p&gt;Laid out, what happened was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;the tool said "duplicate"              →  correct
the AI said "don't consolidate"        →  plausible, and wrong
I opened the file and checked          →  consolidating read better
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;AI can justify not doing something with a plausible reason.&lt;/strong&gt; And the principle it cites is &lt;strong&gt;generally correct&lt;/strong&gt;, which is why you nod along.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Whether an argument is convincing and whether it's right for this code are different questions.&lt;/strong&gt; And the second one &lt;strong&gt;can't be settled until you open the file.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Which brings us back to distance.&lt;/p&gt;

&lt;p&gt;If I had turned this into a ticket that said "reduce duplication" and handed it to someone, &lt;strong&gt;the AI's explanation would probably have won.&lt;/strong&gt; It's well written; there's nothing to argue against. &lt;strong&gt;Only the person who opened the file can overturn it.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;What these two share is that &lt;strong&gt;the right answer wasn't knowable until something actually ran.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example 1&lt;/strong&gt; — &lt;strong&gt;the purpose it was built for&lt;/strong&gt; turned out to be wrong, and only usage revealed it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Example 2&lt;/strong&gt; — a plausible explanation from an AI was, in fact, wrong&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Neither fits in a spec or a ticket.&lt;/strong&gt; By the time it fits, you've already checked.&lt;/p&gt;

&lt;p&gt;What you can decide before building is &lt;strong&gt;only what you already know.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  AI doesn't shorten the distance
&lt;/h2&gt;

&lt;p&gt;This is where the original question comes back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What AI made fast is mostly implementation and planning — and the benefit lands on whoever is at the keyboard.&lt;/strong&gt; If the authority to decide sits far from the keyboard, that speedup doesn't reach it as-is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;authority at the keyboard   →  fast, and corrected as you go
authority still far away    →  fast, and still far away
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;"Fast while still far away" is the worst state.&lt;/strong&gt; When the person judging stays distant, &lt;strong&gt;the speed at which mistakes are noticed barely changes, while the speed at which they accumulate goes up.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The result of that is "&lt;strong&gt;too big to review&lt;/strong&gt;."&lt;/p&gt;

&lt;h3&gt;
  
  
  Why "just have the AI review it" doesn't fix it
&lt;/h3&gt;

&lt;p&gt;AI reads far more text far faster than a human. That's true. &lt;strong&gt;So I won't claim "the AI can't read it."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two things matter, though.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, there's a ceiling on how much it can hold at once&lt;/strong&gt; (the context — how much text the model can look at simultaneously). Depending on the model and your tooling, a large diff may not fit in one pass. Split it up and it fits, but &lt;strong&gt;splitting makes it much harder to surface "A and B don't line up."&lt;/strong&gt; It only ever sees one side. And bugs usually live at exactly that seam.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, passing review doesn't mean the design is good.&lt;/strong&gt; I have a concrete example of what survives after every machine check is green.&lt;/p&gt;

&lt;p&gt;At one point I eliminated every "function that is too long" in my repo. Machines had nothing to complain about. Three months later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CollectionView.vue   2,945 lines   187 functions   longest 61 lines
server/index.ts      3,020 lines   212 functions   longest 82 (most under 10)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;There are no long functions. Nobody is being warned. And the files are 3,000 lines.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because the machine only looks at "how long is this one function," never "how many different jobs live in this one file." &lt;code&gt;CollectionView.vue&lt;/code&gt; had &lt;strong&gt;eleven separate jobs&lt;/strong&gt; in it — list rendering, filtering, editing, chat, and more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;187 small functions added up to 3,000 lines, and the machine never once complained.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"The machine passed it, so it's fine" doesn't hold for review or for design. &lt;strong&gt;And a setup that puts AI review as the last line of defence collapses right here.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  So what do I actually do
&lt;/h2&gt;

&lt;p&gt;Numbers from my own way of working. Again: &lt;strong&gt;this is not proof of correctness, it's observational data about how I work.&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;MulmoTerminal (2026-06-14 → 2026-08-06, 53 days)

  commits        3,235
  merged PRs     1,170
  releases          64      about 1.2 per day
  written by     effectively 2 people (2,990 / 221, plus one at 24)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Time from opening to closing, last 400 closed issues
  median          1.6 hours
  within 6h       77%
  within 24h      92%
  over a week      2%

Diff size, last 400 merged PRs
  median          286 lines
  under 300       51%
  files changed   median 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How I measured&lt;/p&gt;

&lt;p&gt;Closed issues: &lt;code&gt;gh issue list --state closed --limit 400&lt;/code&gt;, taking the difference between created and closed timestamps. PRs: the last 400, additions plus deletions. &lt;strong&gt;Both are cut as "the most recent 400," so if the way I work changed partway through, that shows up in the number.&lt;/strong&gt; Commits and releases are &lt;code&gt;git rev-list --count&lt;/code&gt; and &lt;code&gt;git tag&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Issues shaped like this &lt;strong&gt;are not specs written before building.&lt;/strong&gt; They're notes of something I noticed while using the thing, fixed the same day. That's why they're short-lived. &lt;strong&gt;If everything had been decided up front, the distribution wouldn't look like this.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Also: &lt;strong&gt;all these numbers can show is that things were built fast.&lt;/strong&gt; Whether what got built is good is a separate question. Please read them separately.&lt;/p&gt;

&lt;p&gt;On that basis, &lt;strong&gt;I keep the state from ever producing a giant diff.&lt;/strong&gt; Not "what do I do with unreviewable code" — &lt;strong&gt;don't get there.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  I do write plans. 351 of them. But the AI writes them.
&lt;/h3&gt;

&lt;p&gt;To be clear, I'm not building with nothing written down.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;351 files in plans/
  features 166 / fixes 132 / cleanups 37 / other 16

  278 of them (79%) carry an issue number
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But these are &lt;strong&gt;not specs. They're the steps for clearing one issue.&lt;/strong&gt; And &lt;strong&gt;the AI writes them&lt;/strong&gt; — I read and judge.&lt;/p&gt;

&lt;p&gt;The order looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1  I notice "this is wrong" while using it
2  I open an issue          ← me. deciding what to do
3  the AI writes the steps   ← automatic
4  I read it, correct it     ← me again
5  it gets implemented       ← automatic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The AI can only write step 3 after step 2 is settled.&lt;/strong&gt; For something where what-to-do isn't decided, having it write is pointless. &lt;strong&gt;As above: plausible output appears, and whether to take it is still my call.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Which is to say —&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;You can only write it once the shape is already known.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Our own process turned out to be the inverse statement of the argument. &lt;strong&gt;I'm not rejecting writing things down. I'm keeping to the conditions under which it can be written.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Adding one person doesn't add just one channel
&lt;/h2&gt;

&lt;p&gt;From here on, this is &lt;strong&gt;about having more than one person.&lt;/strong&gt; If you work alone, feel free to skip.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Judgement and alignment grow with the number of channels, not the number of people.&lt;/strong&gt; It's pairs, so for n people it's &lt;code&gt;n(n-1)/2&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;People&lt;/th&gt;
&lt;th&gt;Channels&lt;/th&gt;
&lt;th&gt;vs. 2 people&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;10×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;28&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;28×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;45×&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Go from two to four and you double the hands — and multiply channels by six.&lt;/strong&gt; At eight, four times the hands and &lt;strong&gt;twenty-eight times the channels&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is a simplified model; not everyone talks to everyone. But it does show that &lt;strong&gt;what grows when you add someone isn't only hands.&lt;/strong&gt; Brooks' &lt;em&gt;The Mythical Man-Month&lt;/em&gt; — "adding people to a late project makes it later" — is rooted in the same place.&lt;/p&gt;

&lt;p&gt;The question is &lt;strong&gt;what AI changed and what it didn't.&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;Before
  what you gain   implementation   grows with headcount (n×)
  what you pay    channels         grows as n(n-1)/2
  → there's a point where it stops paying, but below it the gain won

Now
  what you gain   implementation   the AI produces it; barely tied to headcount
  what you pay    channels         still n(n-1)/2. unchanged
  → the gain side shrank; the cost side stayed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;At least the reason "we need more hands to write it" got weaker, while the price of adding people stayed put.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  This connects back to the former CTOs
&lt;/h3&gt;

&lt;p&gt;Earlier I mentioned &lt;strong&gt;CTOs from Workday and You.com joining Anthropic as individual contributors.&lt;/strong&gt; I think that's this formula too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lead a large organisation and you get implementation capacity proportional to headcount.&lt;/strong&gt; At the same time, &lt;strong&gt;you carry &lt;code&gt;n(n-1)/2&lt;/code&gt; channels.&lt;/strong&gt; A decision only becomes real after it has been through all of them.&lt;/p&gt;

&lt;p&gt;What happens if you step off that?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;take it from an organisation   capacity × headcount  +  carry n(n-1)/2 channels
take it from AI                capacity              +  one person's worth of channels
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sit somewhere you can focus purely on judgement and use AI without limits.&lt;/strong&gt; Get the implementation capacity from the AI rather than from an organisation.&lt;/p&gt;

&lt;p&gt;None of them said this, of course, so &lt;strong&gt;this is my reading.&lt;/strong&gt; But looking at &lt;strong&gt;what the people closest to the frontier just gave up and what they went to get&lt;/strong&gt;, it points this way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What they gave up was the organisation. What they went for was distance — between judgement and the AI.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  That said, the formula doesn't give you a number
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;This formula does not produce "so the optimal number is N."&lt;/strong&gt; Channel count is neither productivity nor decision speed. All the formula says is that &lt;strong&gt;one side of the scale got lighter&lt;/strong&gt;; &lt;strong&gt;the specific number comes from my own experience below.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  There's a ceiling on how much &lt;em&gt;you&lt;/em&gt; can run in parallel
&lt;/h3&gt;

&lt;p&gt;The same thing happens inside one person. Honestly, here's mine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Within one project
  just skimming and judging        about 10 is reachable
  thinking about design            5 is the limit

Across projects
  work that takes no thought       3 to 4 projects
  dependencies / design work /
  chasing a repro                  can't parallelise (1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The tool can show nine. The one that can't is me.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And adding "one more, I can probably take it" &lt;strong&gt;slows everything down.&lt;/strong&gt; The closer your own utilisation gets to 100%, &lt;strong&gt;the more sharply the agents' waiting time grows&lt;/strong&gt; (picture a convenience store with one register and a clerk who is essentially never idle — a small bump and the queue stretches).&lt;/p&gt;

&lt;p&gt;So the way to raise parallelism isn't "add agents," it's &lt;strong&gt;reduce the part that can't move without you.&lt;/strong&gt; Decide design up front, write the repro steps first, cut dependencies early. &lt;strong&gt;Though that carving-out is also your work, so it never reaches zero.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Which leads to: teams should be small too
&lt;/h2&gt;

&lt;p&gt;Everything up to here holds for one person. &lt;strong&gt;What follows is the multi-person case.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The variable that should set headcount is not implementation volume. It's the amount of judgement.&lt;/strong&gt; By judgement I mean setting priorities, settling design, deciding how exceptions are handled — &lt;strong&gt;the kind of decision that loses accuracy when handed to someone else.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The premise of sizing by implementation volume has broken down, at least around me, because &lt;strong&gt;implementation volume no longer tracks headcount.&lt;/strong&gt; &lt;strong&gt;What's left is judgement, and splitting judgement is where the formula above bites.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So here's where I land.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If you're building something whose shape isn't settled yet, and the owner can touch the code:&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;one to three people per product.&lt;/strong&gt;&lt;br&gt;
The owner, or someone who can back them, does the implementing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;This is not a universal optimum. It's a conditional rule of thumb.&lt;/strong&gt; It roughly holds when these are true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What to build isn't fully decided&lt;/strong&gt; — you're still exploring the shape&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The product's owner can touch the code&lt;/strong&gt; — the decider is at the keyboard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One person can hold the whole thing in their head&lt;/strong&gt; — when they can't, that's your split line&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You can ship on your own judgement&lt;/strong&gt; — no approval queue&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;If any of those is missing, you can't cut headcount.&lt;/strong&gt; Work that needs deep specialist knowledge, anything running 24/7, industries where the author legally can't review their own work. Those aren't headcount problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  The two-pizza rule isn't about meetings any more
&lt;/h3&gt;

&lt;p&gt;Amazon's &lt;a href="https://docs.aws.amazon.com/whitepapers/latest/introduction-devops-aws/two-pizza-teams.html" rel="noopener noreferrer"&gt;two-pizza team&lt;/a&gt; idea was, originally, a design principle for &lt;strong&gt;small autonomous teams&lt;/strong&gt; — meetings being part of it. When implementation needed people, capping communication on top of that was the rational move.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Implementation no longer scales with headcount.&lt;/strong&gt; What's left is judgement, and judgement is what eats alignment cost.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;then   "we need the people. at least keep meetings and teams small."
now    "we don't need the people. the unit of building can be small."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  There are things you genuinely want more people for
&lt;/h3&gt;

&lt;p&gt;Read this far and it may sound like "cut headcount, always." It isn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The only thing that should be small is the unit of building.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The number of ideas&lt;/strong&gt; — more is better&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The number of people giving you feedback&lt;/strong&gt; — more is better&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The number of people using it&lt;/strong&gt; — more is better&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As in Example 1, &lt;strong&gt;I did not find out that the purpose was wrong until I asked four people.&lt;/strong&gt; That doesn't happen without people around you.&lt;/p&gt;

&lt;p&gt;So it splits like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;the unit of building     smaller is better    because judgement can't be divided
the people around it     more is better       because it grows the material for judgement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;That "around" is your community, and your company.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Separate the place you build from the place opinions gather. &lt;strong&gt;Two people inside is fine; outside, more is better.&lt;/strong&gt; If anything, the smaller you make the unit of building, &lt;strong&gt;the more you need material from outside&lt;/strong&gt; — with fewer people inside, your perspective narrows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cut people per product, not people
&lt;/h3&gt;

&lt;p&gt;Easy to misread, so: &lt;strong&gt;this is not "turn eight people into two."&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;✗  make eight people into two
✓  stop putting eight people on one product
   → 2 people × 4 products
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Reduce the headcount per product, not the headcount.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Though, as above, &lt;strong&gt;three or four projects at once only works for the low-thought work.&lt;/strong&gt; If all of them are in design mode, one is the limit — so &lt;strong&gt;"two people, therefore six to eight products" doesn't follow.&lt;/strong&gt; In practice it's one you're thinking hard about, plus a few you're coasting.&lt;/p&gt;




&lt;h2&gt;
  
  
  In the end, this is what I think it comes down to
&lt;/h2&gt;

&lt;p&gt;I've written a lot above, but the essence is one line.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The product's owner, in a small group, building it while using it themselves in detail.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the shape I think fits building products in the AI era.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you aren't the heaviest user, it won't get good
&lt;/h3&gt;

&lt;p&gt;In Example 1, the purpose I built it for turned out to be wrong. &lt;strong&gt;The only reason I caught that is that I use it every day too.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If I didn't, someone saying "I can handle the same number more easily" would mean nothing to me. &lt;strong&gt;I wouldn't know the weight of the answer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The material for judgement only accumulates through use. &lt;strong&gt;A tool its owner doesn't use can't be fixed by anyone's judgement.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  You probably don't need to be a strong programmer
&lt;/h3&gt;

&lt;p&gt;This is the hopeful part, I think.&lt;/p&gt;

&lt;p&gt;What's required is &lt;strong&gt;being clear about what you want to build&lt;/strong&gt;, and &lt;strong&gt;being able to look at the output and say "no, not this."&lt;/strong&gt; &lt;strong&gt;Not writing code quickly.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you have those two, &lt;strong&gt;an era where non-engineers build good products is coming into view.&lt;/strong&gt; If anything, the clearer you are about what you want, the more the leverage pays.&lt;/p&gt;

&lt;h3&gt;
  
  
  But you can't just hand it off
&lt;/h3&gt;

&lt;p&gt;You give the AI the spec and the implementation, and what comes out is "somehow not right."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Of course it is.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without settling the details yourself, you don't get a product with substance.&lt;/strong&gt; Micromanagement is not usually a compliment when the subject is a person. &lt;strong&gt;With an AI, though, I think it's simply the correct approach.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It doesn't complain. It'll redo it as many times as you like. &lt;strong&gt;So you may as well push until you're satisfied.&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  What this post said
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Rescuing "too big to review" &lt;strong&gt;with review alone is very hard&lt;/strong&gt;. The answer is to not get there&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The cause is writing a big spec up front.&lt;/strong&gt; This happens when you work alone too&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You can't write a spec for something when you don't know if it's possible or whether you want it&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This isn't AI's fault.&lt;/strong&gt; Specs were wrong in the human-only era too, and nobody could tell until they touched it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Even when AI helps write the spec, deciding whether to take it is still you.&lt;/strong&gt; The tool got smarter, not you&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leverage is a multiplier, not the number being multiplied.&lt;/strong&gt; Without something to multiply, it means nothing&lt;/li&gt;
&lt;li&gt;Adding people grows channels as &lt;code&gt;n(n-1)/2&lt;/code&gt;. Hence &lt;strong&gt;one to three per product&lt;/strong&gt; (a conditional rule of thumb)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Handing over a big spec and letting it build in one go is &lt;strong&gt;buying a single lottery ticket where you don't learn the result until the end.&lt;/strong&gt; And if you try to fix that by writing the spec in more detail, where you arrive is implementation. &lt;strong&gt;Same effort — write the one that runs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Rough out the base, then work alongside it, using it yourself in detail. &lt;strong&gt;That isn't "being sloppy." It means increasing the number of judgements.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And the only person who can increase that number is &lt;strong&gt;the one sitting at the keyboard, using the thing themselves.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  One small plug at the end
&lt;/h2&gt;

&lt;p&gt;Every number above came out of building &lt;strong&gt;MulmoTerminal&lt;/strong&gt;. And I'll admit it: &lt;strong&gt;it's also the tool for working the way this post describes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cut small and draw often, and this always happens:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Wait — which one is waiting on me right now?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Run three or four agents and &lt;strong&gt;you'll leave one waiting for tens of minutes without noticing.&lt;/strong&gt; Since your own attention is what caps parallelism, everything stalls when that clogs.&lt;/p&gt;

&lt;p&gt;I built it to solve that with &lt;strong&gt;cell colour, sound, and a push to your phone.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This tool is specialised for how far one person can run in parallel.&lt;/strong&gt; There is nothing in it for splitting work across a team. &lt;strong&gt;If you're comfortable in a terminal and don't mind working in parallel&lt;/strong&gt;, it probably suits you best.&lt;/p&gt;

&lt;h3&gt;
  
  
  In practice, ten is reachable
&lt;/h3&gt;

&lt;p&gt;Earlier I wrote "measured, it's one to six." That was &lt;strong&gt;the result of asking four people.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I myself run &lt;strong&gt;about ten when I'm purely skimming and judging.&lt;/strong&gt; Someone at a US startup I spoke to said &lt;strong&gt;twelve.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That may look like a contradiction. It isn't.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;thinking about design       5 is the limit
just skimming and judging   about 10 is reachable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Mix them and you get dragged down to the lower number.&lt;/strong&gt; Which also means: &lt;strong&gt;reduce the things that can't move without you, and the number goes up.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Ten to twenty for one person.&lt;/strong&gt; If you're comfortable in a terminal and don't mind parallelism, &lt;strong&gt;I think that's reachable.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Getting there doesn't take more agents. It takes &lt;strong&gt;exactly what this post has been about&lt;/strong&gt;: cut to the smallest unit that needs a judgement, cut dependencies early, write the repro steps first. &lt;strong&gt;Shave down the part that can't move without you, as far as it will go.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What raises the ceiling isn't the tool. It's how tightly you work.&lt;/strong&gt; Give it a go.&lt;/p&gt;

&lt;h3&gt;
  
  
  But the tool alone won't get you to ten
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;If you go and read every one of them thinking "is this okay?", you'll cap out at five.&lt;/strong&gt; To grow the number you have to &lt;strong&gt;reduce the attention each one costs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What I do for that is written up in the other posts in this series (Japanese):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/isamu/i-stopped-reviewing-my-own-code-heres-what-had-to-be-true-first-4nh0"&gt;I stopped reviewing my own code&lt;/a&gt; — the whole picture of &lt;strong&gt;not breaking without reading&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/isamu/green-is-not-evidence-two-of-my-checks-were-covering-less-than-i-thought-j99"&gt;Green is not evidence&lt;/a&gt; — whether &lt;strong&gt;the checks you have are actually checking anything&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Without that machinery in place, ten in parallel doesn't hold.&lt;/strong&gt; With it, &lt;strong&gt;your attention goes to judgement only.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's all connected.&lt;/strong&gt; "Cut small and run in parallel" here, and "don't break without reading" over there, &lt;strong&gt;are the same one thing written from two sides.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It runs on your own machine and &lt;strong&gt;you look at it in a browser.&lt;/strong&gt; It starts with &lt;code&gt;npx&lt;/code&gt;, so there's nothing to install first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx mulmoterminal@latest
&lt;span class="c"&gt;# → http://localhost:34567&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;→ &lt;strong&gt;&lt;a href="https://github.com/receptron/mulmoterminal" rel="noopener noreferrer"&gt;receptron/mulmoterminal&lt;/a&gt;&lt;/strong&gt; (MIT)&lt;/p&gt;

&lt;p&gt;If you're running things in parallel on your own, or with a small group, it may well help.&lt;/p&gt;




</description>
      <category>ai</category>
      <category>productivity</category>
      <category>programming</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>When your GUI isn't the whole input language</title>
      <dc:creator>Isamu Arimoto</dc:creator>
      <pubDate>Mon, 03 Aug 2026 20:59:42 +0000</pubDate>
      <link>https://dev.to/isamu/when-your-gui-isnt-the-whole-input-language-2a05</link>
      <guid>https://dev.to/isamu/when-your-gui-isnt-the-whole-input-language-2a05</guid>
      <description>&lt;p&gt;I spent a week of my life on where a button should go. I don't think I'll do that again, and it took porting a benchmark tool to work out why.&lt;/p&gt;

&lt;p&gt;Let me concede the obvious thing first, because otherwise it's the only thing anyone will want to talk about: &lt;strong&gt;GUIs are still input devices.&lt;/strong&gt; Direct manipulation, menus, palettes, keyboard shortcuts, drag targets, accessibility affordances — all input, all irreplaceable. I'm not arguing for chat-instead-of-UI. Command palettes, scripting and macro systems have absorbed open-ended commands inside GUIs for decades, and they work.&lt;/p&gt;

&lt;p&gt;The argument is narrower, and it's about a specific failure: &lt;strong&gt;designing controls first, for a system whose set of valid operations won't hold still.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I noticed
&lt;/h2&gt;

&lt;p&gt;I'd built a benchmark tool. It evaluates combinations of &lt;strong&gt;LLM/SLM models × skills&lt;/strong&gt;, and scores each combination on four things: accuracy, runtime, turn count, and total cost.&lt;/p&gt;

&lt;p&gt;It also tracks the moving parts. &lt;strong&gt;New models arrive constantly. Prices change constantly.&lt;/strong&gt; Both are part of the tool's state.&lt;/p&gt;

&lt;p&gt;It ran inside Claude Code. I decided to port it to a proper web tool, sat down to design the interface, and got about as far as two dropdowns before it fell apart.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The two dropdowns were fine.&lt;/strong&gt; Pick a model. Pick a skill set. Both are finite, both need to be &lt;em&gt;shown&lt;/em&gt; to you — you can't ask for a model if you don't know it exists — and both are cheap to render.&lt;/p&gt;

&lt;p&gt;Everything else was the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the work actually is
&lt;/h2&gt;

&lt;p&gt;The tool isn't really "run a benchmark." It's &lt;em&gt;maintenance&lt;/em&gt;. Every time a model or a skill is added or updated, work appears:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A model was added. Re-run it against which skills? All of them, or the ones where the previous best is close? Compare against which baseline?&lt;/li&gt;
&lt;li&gt;A price changed. Recompute historical cost, or only from here? Are last month's cost comparisons still meaningful?&lt;/li&gt;
&lt;li&gt;A skill was updated. Which stored results are now invalid? Re-run the whole matrix, or the affected column?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I kept trying to design controls for these and kept discovering the same thing: &lt;strong&gt;each one is a slightly different task.&lt;/strong&gt; Not a variant of a task — a different task, shaped by what changed and what I already knew.&lt;/p&gt;

&lt;p&gt;To put that in a UI I'd have to enumerate them. Every one, in advance, before knowing what the next model release would make me want. And each new axis creates more cross-combinations with all the others.&lt;/p&gt;

&lt;p&gt;In chat it's a sentence. &lt;em&gt;"gpt-5-mini is in — run it against every skill and put it next to haiku."&lt;/em&gt; The agent works out which runs are actually needed. Nothing was designed for that request specifically, and nothing had to be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That's the whole of it.&lt;/strong&gt; Not "chat is better." Just: when the set of valid operations keeps changing, a control surface has to keep growing, and it's always one release behind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the line fell
&lt;/h2&gt;

&lt;p&gt;Not "finite versus infinite." I had it wrong at first. The line is:&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;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;UI keeps it&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Things that must be &lt;strong&gt;enumerated and shown&lt;/strong&gt; — available models, available skills, the price table, the four scores&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Chat takes it&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Things where &lt;strong&gt;the task is different every time&lt;/strong&gt; — what to re-run, against which baseline, which results to invalidate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The model list has to be a UI, and not because it's short. It's because &lt;strong&gt;you cannot ask for something you don't know exists.&lt;/strong&gt; Enumeration is the whole value.&lt;/p&gt;

&lt;p&gt;The four scores are the same. Accuracy, runtime, turns, cost — you read them. They update when a price moves. There is nothing to click, and clicking would not help.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I didn't expect
&lt;/h2&gt;

&lt;p&gt;When a user gets stuck in a GUI, you often get nothing usable. They can't find the control, conclude the thing can't be done, and leave. You may see that they dropped off; &lt;strong&gt;you rarely learn what they were trying to do.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a user gets stuck in chat, they type what they were trying to do. Now you have a sentence describing a task your product doesn't support, in their words.&lt;/p&gt;

&lt;p&gt;I want to be careful here, because there's an obvious objection and it's correct: &lt;strong&gt;chat users also leave silently.&lt;/strong&gt; Plenty do. And GUIs have telemetry, funnels, session recordings — this isn't a categorical difference, and I'd be overselling it if I said it was.&lt;/p&gt;

&lt;p&gt;What it is, I think, is a difference in &lt;em&gt;recovery&lt;/em&gt;. A failed GUI interaction usually leaves you an event, not an intention. A failed chat interaction leaves a sentence, which means someone — you, a support person, or the agent itself — has something to answer while the user is still there. Not always. But it's the difference between a signal you have to interpret and one that states itself.&lt;/p&gt;

&lt;p&gt;For a tool whose task list changes every week, that mattered more than I expected. The unsupported requests &lt;em&gt;are&lt;/em&gt; the roadmap.&lt;/p&gt;

&lt;h2&gt;
  
  
  A second data point, honestly labelled
&lt;/h2&gt;

&lt;p&gt;I also maintain a browser terminal grid for supervising several coding agents at once. Different product, different problem, built for different reasons.&lt;/p&gt;

&lt;p&gt;Counting one cell's header: roughly &lt;strong&gt;11 buttons and 9 status chips.&lt;/strong&gt; The chips are directory, git branch and change count, model and context percentage, token counts, which PR or issue the cell is on, a diff badge, usage, and a one-line summary of what the agent is currently doing. &lt;strong&gt;None of them is clickable in that configuration.&lt;/strong&gt; They are read.&lt;/p&gt;

&lt;p&gt;And every button that survived does the same kind of thing: expand, close, show files, open the canvas, open the timeline. &lt;strong&gt;They change where you look.&lt;/strong&gt; Not one of them makes the agent do anything — that gets typed.&lt;/p&gt;

&lt;p&gt;The same boundary appeared in another product, one I wasn't thinking about the benchmark tool while building.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But I should not call that convergence.&lt;/strong&gt; Same author, same period, same tools, same assumptions. It's a personal design pattern showing up twice, not independent validation. Take it as evidence that the boundary isn't specific to one benchmark workflow, and no more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this stops being true
&lt;/h2&gt;

&lt;p&gt;Precisely, because a claim that never fails isn't saying much:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Precision and spatial feedback.&lt;/strong&gt; You do not describe a crop, a curve, or a fillet. Photo editors and CAD tools aren't going to move that to a prompt, and shouldn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repetition and muscle memory.&lt;/strong&gt; An action you take fifty times a day should be one keystroke. Typing it is a downgrade every single time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discovery.&lt;/strong&gt; A new user doesn't know what to ask for. This is the strongest argument for keeping the control surface, and it's why the model list stayed a list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safety.&lt;/strong&gt; "Delete the failed runs" should require pointing at something, not a sentence that could be parsed generously.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Spreadsheets, IDEs and design tools stay hybrids. They should. Chat is an orchestration layer over stable primitives, not a replacement for them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thesis, as narrowly as I can put it
&lt;/h2&gt;

&lt;p&gt;A visual interface is excellent at exposing &lt;strong&gt;stable objects, state, constraints, and high-frequency actions.&lt;/strong&gt; It becomes expensive and brittle when the valid operations are numerous, compositional, state-dependent, and continuously changing.&lt;/p&gt;

&lt;p&gt;In those systems: &lt;strong&gt;let the UI enumerate what has to be chosen and inspected, and let a conversational layer absorb the requests that would otherwise need a new control every week.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The gain isn't that chat replaces the interface. It's that changing intent becomes &lt;strong&gt;observable, recoverable and extensible&lt;/strong&gt; without shipping a button for every new combination.&lt;/p&gt;

&lt;p&gt;So: stop agonising over button placement — in the parts of your product where the verbs won't hold still. Everywhere else, the button is still the right answer — as it has been the whole time.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you've shipped a control surface for a workflow that kept changing underneath it, I'd like to hear how that went. My honest position is that I found the boundary in two of my own products and I don't yet know how far it generalises.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ux</category>
      <category>ai</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Green is not evidence. Two of my checks were covering less than I thought.</title>
      <dc:creator>Isamu Arimoto</dc:creator>
      <pubDate>Mon, 03 Aug 2026 08:25:55 +0000</pubDate>
      <link>https://dev.to/isamu/green-is-not-evidence-two-of-my-checks-were-covering-less-than-i-thought-j99</link>
      <guid>https://dev.to/isamu/green-is-not-evidence-two-of-my-checks-were-covering-less-than-i-thought-j99</guid>
      <description>&lt;p&gt;Someone left this on &lt;a href="https://dev.to/isamu/i-stopped-reviewing-my-own-code-heres-what-had-to-be-true-first-4nh0"&gt;a post of mine&lt;/a&gt; about how I stopped reviewing my own code:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Most teams test the output of a rule (e.g., does this code pass linting?) but rarely test the rule itself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'd been writing about pulling decisions into pure functions so they can be tested. They took it one level up: fine, but who tests the thing doing the checking?&lt;/p&gt;

&lt;p&gt;I had two answers to that lying around. I just hadn't connected them to each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first one: a typecheck that checked half the repo
&lt;/h2&gt;

&lt;p&gt;We run &lt;code&gt;yarn typecheck&lt;/code&gt; in CI on every PR. It had been green for months.&lt;/p&gt;

&lt;p&gt;It had also never looked at our server code, or any of our tests.&lt;/p&gt;

&lt;p&gt;The root &lt;code&gt;tsconfig.json&lt;/code&gt; referenced two of five projects. &lt;code&gt;vue-tsc -b&lt;/code&gt; walks the references it's given, so everything else was simply out of scope. Not skipped with a warning — never enumerated in the first place.&lt;/p&gt;

&lt;p&gt;Here's the part I want to underline. I fixed the references — and the check stayed green, exactly as green as before. Of course it did; the code was fine. &lt;strong&gt;Green looks identical whether you're checking five projects or two&lt;/strong&gt;, which means it could not tell me whether my fix had worked. So I did this:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nope&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of those in each of the four areas I cared about, then run the check and confirm it fails.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Where I planted it&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;server/config/workspace.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;passed&lt;/td&gt;
&lt;td&gt;✗ caught&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/utils/focusTrap.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✗ caught&lt;/td&gt;
&lt;td&gt;✗ caught&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;test/common/readString.spec.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;passed&lt;/td&gt;
&lt;td&gt;✗ caught&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;test/server/git/prs.spec.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;passed&lt;/td&gt;
&lt;td&gt;✗ caught&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three of four had been silently passing. The one that already worked is why nobody noticed — you get &lt;em&gt;some&lt;/em&gt; type errors, so the check is obviously running, so you stop wondering. (Fixed now: the root config references all five.)&lt;/p&gt;

&lt;p&gt;There's a footnote to this. Our contributing guide had a line saying "&lt;code&gt;yarn typecheck&lt;/code&gt; alone passes while CI fails — run all three commands." Somebody had hit this, worked around it in prose, and moved on. &lt;strong&gt;A rule in your docs that tells people to be careful is often a bug you can fix in config.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The second one: a rule set to &lt;code&gt;error&lt;/code&gt; that reports zero
&lt;/h2&gt;

&lt;p&gt;This one is better, because there is nothing to fix in the config. The config is correct.&lt;/p&gt;

&lt;p&gt;We ban type assertions. Not "discourage" — banned, at error severity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npx eslint &lt;span class="nt"&gt;--print-config&lt;/span&gt; src/components/SettingsField.vue &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;    | jq '.rules["@typescript-eslint/consistent-type-assertions"]'

[2, {"assertionStyle": "never"}]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Severity 2. Error. For that exact file.&lt;/p&gt;

&lt;p&gt;That exact file, line 14:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;@input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npx eslint src/components/SettingsField.vue
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing. &lt;code&gt;yarn lint&lt;/code&gt; reported &lt;strong&gt;0 errors&lt;/strong&gt; across the repo.&lt;/p&gt;

&lt;p&gt;Both facts are true and neither is a bug. &lt;code&gt;vue-eslint-parser&lt;/code&gt; exposes the &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; as a separate AST, and typescript-eslint rules don't walk it. They see &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;. A cast in a template was never in scope for the rule — it just looks like it is, because &lt;code&gt;--print-config&lt;/code&gt; will happily tell you the rule is on.&lt;/p&gt;

&lt;p&gt;I measured the other frameworks, expecting this to be a template-language problem generally. It isn't:&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;script side&lt;/th&gt;
&lt;th&gt;template / JSX side&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;React, Solid, Preact (&lt;code&gt;.tsx&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Svelte&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Astro&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Vue&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;JSX is part of TypeScript's own grammar, so it lands in the same tree. The Svelte and Astro parsers expose their template expressions in a way these rules can visit. Vue keeps them separate. It's an implementation choice, not something inherent to having a template language — which is what I'd assumed before measuring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And it's fixable.&lt;/strong&gt; typescript-eslint can't reach that AST, but &lt;code&gt;eslint-plugin-vue&lt;/code&gt; can — &lt;code&gt;vue/no-restricted-syntax&lt;/code&gt; is the one rule that walks it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;files&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="s2"&gt;**/*.vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;rules&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="s2"&gt;vue/no-restricted-syntax&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&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;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TSAsExpression&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Narrow in &amp;lt;script&amp;gt;, pass the result to the template.&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;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TSNonNullExpression&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Same.&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One subtlety worth copying: exclude &lt;code&gt;as const&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TSAsExpression:not([typeAnnotation.typeName.name="const"])&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;consistent-type-assertions&lt;/code&gt; already exempts it, and the two halves of one SFC must not disagree about what's banned. It's also not what the ban is for — a const assertion narrows a literal the compiler can already see, rather than claiming a type it couldn't prove.&lt;/p&gt;

&lt;p&gt;We ran this on a sibling project first and it found &lt;strong&gt;16&lt;/strong&gt; casts that had been outside the gate the entire time the ban was at error. Six were already unnecessary: a &lt;code&gt;v-if&lt;/code&gt; above them had started narrowing the type at some point, and nobody re-checked, because nothing was looking.&lt;/p&gt;

&lt;p&gt;The rest moved into &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;, which is where a DOM type check belongs anyway:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;input=&lt;/span&gt;&lt;span class="s"&gt;"onInput"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// bail if it isn't&lt;/span&gt;
  &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;update:modelValue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&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;The cast wasn't protecting anything. It was hiding that the check had been written in the wrong place.&lt;/p&gt;

&lt;p&gt;Same file, same line, after the rule went in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npx eslint src/components/SettingsField.vue
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;  18:41  error  Do not use type assertions — narrow in &amp;lt;script&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;and pass
&lt;span class="go"&gt;                the result to the template   vue/no-restricted-syntax

✖ 1 problem (1 error, 0 warnings)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole point of this post, in one command. &lt;strong&gt;The config didn't get stricter — &lt;code&gt;consistent-type-assertions&lt;/code&gt; was already at error.&lt;/strong&gt; What changed is that something is now looking at the place where the violation lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this keeps happening: the config composes
&lt;/h2&gt;

&lt;p&gt;Neither failure was carelessness, and I don't think either was avoidable by being more careful. Lint and compiler config compose, and composition is where simple pieces stop behaving simply.&lt;/p&gt;

&lt;p&gt;Two examples I only found by measuring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;strict&lt;/code&gt; doesn't include what you'd guess.&lt;/strong&gt; It's eight flags. These six are not among them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;noUncheckedIndexedAccess&lt;/span&gt;        &lt;span class="err"&gt;exactOptionalPropertyTypes&lt;/span&gt;
&lt;span class="err"&gt;noImplicitReturns&lt;/span&gt;               &lt;span class="err"&gt;noPropertyAccessFromIndexSignature&lt;/span&gt;
&lt;span class="err"&gt;noImplicitOverride&lt;/span&gt;              &lt;span class="err"&gt;noFallthroughCasesInSwitch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The rule that bans &lt;code&gt;as&lt;/code&gt; is not in &lt;code&gt;strict&lt;/code&gt;.&lt;/strong&gt; I loaded each typescript-eslint preset and read back what was enabled:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;rule&lt;/th&gt;
&lt;th&gt;&lt;code&gt;recommended&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;strict&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;stylistic&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;recommendedTypeChecked&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;strictTypeChecked&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no-explicit-any&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no-non-null-assertion&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;consistent-type-assertions&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;—&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;✅&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;—&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no-floating-promises&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no-unsafe-assignment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It lives in &lt;code&gt;stylistic&lt;/code&gt; only. Not in &lt;code&gt;strict&lt;/code&gt;, not even in &lt;code&gt;strictTypeChecked&lt;/code&gt;. If you reached for &lt;code&gt;strict&lt;/code&gt; because casts worried you — which is exactly what I did — you got the opposite of what you wanted, and nothing told you.&lt;/p&gt;

&lt;p&gt;Then there's a second-order version, which is the one that actually unsettled me.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sonarjs/different-types-comparison&lt;/code&gt; flagged nine comparisons as always-false. All nine were false positives, and they looked like this:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* the rule says this can't happen */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without &lt;code&gt;noUncheckedIndexedAccess&lt;/code&gt;, TypeScript types &lt;code&gt;process.argv[2]&lt;/code&gt; as &lt;code&gt;string&lt;/code&gt;. The rule believed it and concluded the guard was dead. &lt;strong&gt;Following the advice would have deleted a real check.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Turning that flag on took it from nine to four — and the remaining four turned out to be genuinely redundant. The rule was never wrong. It was reasoning correctly from a type that was lying to it.&lt;/p&gt;

&lt;p&gt;So: &lt;strong&gt;when a rule produces false positives, suspect a missing setting upstream before you suspect the rule.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The method, such as it is
&lt;/h2&gt;

&lt;p&gt;Three commands. None of them take more than a few seconds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# what is ACTUALLY enabled for this file, after all inheritance&lt;/span&gt;
npx eslint &lt;span class="nt"&gt;--print-config&lt;/span&gt; src/index.ts

&lt;span class="c"&gt;# effective tsconfig, after all extends&lt;/span&gt;
./node_modules/.bin/tsc &lt;span class="nt"&gt;-p&lt;/span&gt; tsconfig.app.json &lt;span class="nt"&gt;--showConfig&lt;/span&gt;

&lt;span class="c"&gt;# how many errors would this flag produce, before I commit to it&lt;/span&gt;
./node_modules/.bin/tsc &lt;span class="nt"&gt;-p&lt;/span&gt; tsconfig.app.json &lt;span class="nt"&gt;--noEmit&lt;/span&gt; &lt;span class="nt"&gt;--pretty&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--noUncheckedIndexedAccess&lt;/span&gt; 2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"error TS"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three notes, each from getting it wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use the project's own &lt;code&gt;tsc&lt;/code&gt;.&lt;/strong&gt; A global one installed by a package manager can be a completely different program wearing the same name, and it will tell you so in a way you won't expect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pass &lt;code&gt;--pretty false&lt;/code&gt;.&lt;/strong&gt; With pretty output, &lt;code&gt;tsc&lt;/code&gt; writes colour escapes &lt;em&gt;between&lt;/em&gt; the two words you're grepping for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;- \x1b[91merror\x1b[0m\x1b[90m TS2322: \x1b[0mType 'string' is not …
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The literal string &lt;code&gt;error TS&lt;/code&gt; isn't in there any more, so &lt;code&gt;grep -c "error TS"&lt;/code&gt; returns &lt;strong&gt;0&lt;/strong&gt; — which reads exactly like "no errors." Mine didn't drop colour when piped, either. (And &lt;code&gt;grep -c&lt;/code&gt; counts matching &lt;em&gt;lines&lt;/em&gt;, so treat it as a gauge, not a census.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Point &lt;code&gt;-p&lt;/code&gt; at a real project, not a solution file.&lt;/strong&gt; This one is the same bug as the whole post, and I walked into it while writing this section. Our root &lt;code&gt;tsconfig.json&lt;/code&gt; is &lt;code&gt;files: []&lt;/code&gt; plus five &lt;code&gt;references&lt;/code&gt; — a solution file. &lt;code&gt;-p&lt;/code&gt; doesn't follow references, so it compiles nothing and exits clean. I planted one error in &lt;code&gt;server/&lt;/code&gt; and measured:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;command&lt;/th&gt;
&lt;th&gt;errors reported&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;tsc -p tsconfig.json --noEmit&lt;/code&gt; (the root)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tsc -b tsconfig.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;72&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tsc -p tsconfig.server.json --noEmit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Zero, from a repo with a deliberate type error in it. If your root is a solution file, &lt;code&gt;-p&lt;/code&gt; on it is a green light that means nothing — use &lt;code&gt;-b&lt;/code&gt;, or name the leaf project.&lt;/p&gt;

&lt;p&gt;Then the part no command does for you:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Break it on purpose.&lt;/strong&gt; &lt;code&gt;--print-config&lt;/code&gt; tells you what the config says. It cannot tell you whether the rule can reach your code, and &lt;code&gt;-p&lt;/code&gt; can't tell you whether it compiled any. The only thing that answers either question is planting a violation and watching the check fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  What turned up once the checks were actually running
&lt;/h2&gt;

&lt;p&gt;The point of all this is that the checks then find things. A sample of what came out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A type claimed a field the API never sent.&lt;/strong&gt; &lt;code&gt;/api/session/:id&lt;/code&gt; doesn't return &lt;code&gt;id&lt;/code&gt;. Adding a runtime guard broke four tests, which is how we found out. The type had said &lt;code&gt;id&lt;/code&gt; was there since the beginning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;fetchJson&amp;lt;T&amp;gt;&lt;/code&gt; returned whatever the caller named it.&lt;/strong&gt; No validation. If you wrote &lt;code&gt;fetchJson&amp;lt;Config&amp;gt;(...)&lt;/code&gt;, you got &lt;code&gt;Config&lt;/code&gt; — as a claim, not a fact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Config was validated on save and not on load.&lt;/strong&gt; Broken entries came straight back in at startup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;String(x ?? "")&lt;/code&gt;, seventeen times.&lt;/strong&gt; When &lt;code&gt;x&lt;/code&gt; is an object you get &lt;code&gt;"[object Object]"&lt;/code&gt;, no exception. That string was used as a lookup key and rendered as a session title. The real damage isn't the display — it's that &lt;em&gt;missing&lt;/em&gt; and &lt;em&gt;corrupted&lt;/em&gt; stop being distinguishable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An &lt;code&gt;await&lt;/code&gt; on a synchronous function.&lt;/strong&gt; It waited for nothing and told every future reader "this line is I/O."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A sort whose answer depended on who ran it.&lt;/strong&gt; A rule suggested &lt;code&gt;localeCompare&lt;/code&gt; for filenames — which are zero-padded dates and ISO timestamps. Locale order would have made the result machine-dependent. Following that advice would have introduced the bug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A guard that could never fire.&lt;/strong&gt; &lt;code&gt;matchAll&lt;/code&gt; always populates &lt;code&gt;index&lt;/code&gt;, per spec.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And on the server side, where I cleared 145 of the 407 &lt;code&gt;no-unsafe-*&lt;/code&gt; findings, they came from just three entry points:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;entry point&lt;/th&gt;
&lt;th&gt;why it's &lt;code&gt;any&lt;/code&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;await import(name)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;a dynamic import with a computed specifier returns &lt;code&gt;any&lt;/code&gt;, and everything reached through it stays outside the type checker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;JSON.parse(...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;returns &lt;code&gt;any&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;req.body&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Express types it &lt;code&gt;any&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All three are "a value from outside." One validating function at each boundary cleared all 145. The client side had a wider spread — socket frames, &lt;code&gt;Response.json()&lt;/code&gt; — but the shape was the same every time: a boundary where an untyped value walked in and nothing stopped to look at it.&lt;/p&gt;

&lt;p&gt;If you only do one thing from this post, grep for those three.&lt;/p&gt;

&lt;p&gt;One trap in there: &lt;code&gt;typeof x === "function"&lt;/code&gt; doesn't narrow enough. You get &lt;code&gt;Function&lt;/code&gt;, and calling a &lt;code&gt;Function&lt;/code&gt; returns &lt;code&gt;any&lt;/code&gt;, so the result escapes again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not everything is worth turning on
&lt;/h2&gt;

&lt;p&gt;Two flags I measured and deliberately left off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;noImplicitReturns&lt;/code&gt;&lt;/strong&gt; — 58 findings, and nearly all of them are this:&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bad&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// no explicit return&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's correct Express. Nobody reads a handler's return value. Satisfying the flag means adding 58 meaningless &lt;code&gt;return&lt;/code&gt;s and closing zero holes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;noPropertyAccessFromIndexSignature&lt;/code&gt;&lt;/strong&gt; — 1,785 findings, all of them &lt;code&gt;obj.key&lt;/code&gt; → &lt;code&gt;obj["key"]&lt;/code&gt;. Access safety is unchanged.&lt;/p&gt;

&lt;p&gt;Compare with &lt;code&gt;noUncheckedIndexedAccess&lt;/code&gt;: 118 findings in shipped code, every one of them "write down what the code already assumed." All 118 were worth it.&lt;/p&gt;

&lt;p&gt;The count isn't the signal. &lt;strong&gt;The question is whether the work closes a hole&lt;/strong&gt;, and 118 real fixes beat 1,785 renames.&lt;/p&gt;

&lt;p&gt;(Tests got their own answer: &lt;code&gt;noUncheckedIndexedAccess&lt;/code&gt; is off there. A test indexing its own fixture with &lt;code&gt;rows[0]&lt;/code&gt; doesn't need &lt;code&gt;T | undefined&lt;/code&gt; — the test is the thing guaranteeing that value. That's 233 findings that were pure noise. &lt;code&gt;exactOptionalPropertyTypes&lt;/code&gt; stays on in tests, because that one is about meaning, not fixture ergonomics.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing I'd tell myself six months ago
&lt;/h2&gt;

&lt;p&gt;Both failures had the same shape, and so did the sibling project's — where &lt;code&gt;yarn lint&lt;/code&gt; turned out never to reach &lt;code&gt;scripts/&lt;/code&gt;, &lt;code&gt;batch/&lt;/code&gt; or &lt;code&gt;config/&lt;/code&gt;. That's 21 files, 92 errors, nine of them the very casts we'd banned. &lt;strong&gt;The code that decides whether a PR can merge had no gate on it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Gates don't fail loudly. A gate that isn't running produces the same output as a gate that's running and finding nothing. There is no error message for "this never executed," because from the inside those two states look identical.&lt;/p&gt;

&lt;p&gt;So the commenter had it right, and their framing is better than what I'd written. Testing that your code passes the check is not the same as testing the check.&lt;/p&gt;

&lt;p&gt;You can't fix that by reading configs more carefully. You fix it by making the check fail on purpose, once, and watching.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Anything you've found this way, I'd like to hear it — particularly if your green was hiding something dumber than mine.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>eslint</category>
      <category>testing</category>
      <category>devops</category>
    </item>
    <item>
      <title>Two things broke when I shipped my Claude Code plugin — and one of my fixes was cargo cult</title>
      <dc:creator>Isamu Arimoto</dc:creator>
      <pubDate>Mon, 03 Aug 2026 06:26:37 +0000</pubDate>
      <link>https://dev.to/isamu/two-things-broke-when-i-shipped-my-claude-code-plugin-and-one-of-my-fixes-was-cargo-cult-78p</link>
      <guid>https://dev.to/isamu/two-things-broke-when-i-shipped-my-claude-code-plugin-and-one-of-my-fixes-was-cargo-cult-78p</guid>
      <description>&lt;p&gt;My skills worked. I'd been using them locally for weeks. Then I packaged them as a plugin, installed it the way a stranger would, and hit things that don't exist locally — and that don't produce an error message pointing at the cause.&lt;/p&gt;

&lt;p&gt;Two of them were real. The third was a rule I'd been repeating for months, which turned out to be about something else entirely. Writing this post is what made me check it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Your skill's name changes, and skill-to-skill calls break silently
&lt;/h2&gt;

&lt;p&gt;A skill in &lt;code&gt;~/.claude/skills/story/SKILL.md&lt;/code&gt; is invoked as:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The same file, shipped inside a plugin named &lt;code&gt;mulmocast&lt;/code&gt;, is invoked as:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The plugin name becomes a prefix.&lt;/strong&gt; Which is fine, until you remember that skills can call other skills.&lt;/p&gt;

&lt;p&gt;My &lt;code&gt;mulmocast&lt;/code&gt; skill is a router: it reads what you asked for and hands off to &lt;code&gt;story&lt;/code&gt;, &lt;code&gt;narrate&lt;/code&gt;, &lt;code&gt;illustrate&lt;/code&gt; and so on. Written the obvious way, that hand-off says &lt;code&gt;story&lt;/code&gt;. So:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;locally&lt;/strong&gt; — &lt;code&gt;mulmocast&lt;/code&gt; → &lt;code&gt;story&lt;/code&gt; → works&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;installed from a marketplace&lt;/strong&gt; — &lt;code&gt;mulmocast&lt;/code&gt; → &lt;code&gt;story&lt;/code&gt; → &lt;strong&gt;no such skill&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I never saw an error that named the cause. The hand-off simply didn't happen, and the skill carried on as though that step had been optional.&lt;/p&gt;

&lt;p&gt;The fix is to write both names, qualified one first:&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;1.&lt;/span&gt; Try &lt;span class="sb"&gt;`mulmocast:story`&lt;/span&gt; → if not found, try &lt;span class="sb"&gt;`story`&lt;/span&gt;
&lt;span class="p"&gt;2.&lt;/span&gt; Try &lt;span class="sb"&gt;`mulmocast:narrate`&lt;/span&gt; → if not found, try &lt;span class="sb"&gt;`narrate`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One note on notation: a user types &lt;code&gt;/story&lt;/code&gt; to invoke a skill. Inside a &lt;code&gt;SKILL.md&lt;/code&gt; you are naming a skill for the model to dispatch to, not typing a slash command — so write it without the slash, as above.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Your CLI's name changes too
&lt;/h2&gt;

&lt;p&gt;Same shape of problem, different layer.&lt;/p&gt;

&lt;p&gt;My skills drive a CLI that ships as its own npm package. The plugin repo carries the skills, references and example scripts — not the CLI. While developing the CLI, I ran it from inside &lt;em&gt;its&lt;/em&gt; repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn run cli images script.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That line is fine in a terminal I own and wrong in a &lt;code&gt;SKILL.md&lt;/code&gt;, because the person reading it installed a plugin. They don't have that checkout, so &lt;code&gt;yarn run cli&lt;/code&gt; resolves to nothing.&lt;/p&gt;

&lt;p&gt;The obvious fix is to write the published &lt;code&gt;bin&lt;/code&gt; name instead:&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;"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;"mulmocast"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bin"&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;"mulmo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lib/cli/bin.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"mulmocast"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lib/cli/bin.js"&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;That works — if they installed the package globally. Which they may not have, and which you have no way to check from inside a skill.&lt;/p&gt;

&lt;p&gt;So what my skills actually say is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx mulmocast@latest movie script.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;npx&lt;/code&gt; resolves the package and downloads a temporary copy when what's installed doesn't satisfy the spec. &lt;strong&gt;No install step to document, and no version to keep in sync with your prose.&lt;/strong&gt; If you keep a &lt;code&gt;yarn run cli&lt;/code&gt; line at all, mark it as the one for people working in the repo — it is not a fallback for your users, it's a note to yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Both of these have the same root cause:&lt;/strong&gt; you are writing instructions from inside an environment your reader is not in. Every name you put in a &lt;code&gt;SKILL.md&lt;/code&gt; — a skill, a command, a path — gets resolved somewhere else.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The one I was wrong about
&lt;/h2&gt;

&lt;p&gt;Here is advice I had been giving, including in writing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Never give your marketplace and your plugin the same &lt;code&gt;name&lt;/code&gt;. If you do, installation fails on Linux with an &lt;code&gt;EXDEV&lt;/code&gt; error.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I believed it. I set my repo up that way. I couldn't tell you where I first picked it up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I don't use Linux.&lt;/strong&gt; I never hit this, never reproduced it, never checked it. Writing this post is what finally sent me looking for the source — &lt;a href="https://github.com/anthropics/claude-code/issues/14799" rel="noopener noreferrer"&gt;anthropics/claude-code#14799&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It has nothing to do with names.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Error: Failed to install: EXDEV: cross-device link not permitted,
&lt;/span&gt;&lt;span class="gp"&gt;rename '/home/user/.claude/plugins/cache/…' -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'/tmp/claude-plugin-temp-…'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On most current Linux distributions &lt;code&gt;/tmp&lt;/code&gt; is &lt;strong&gt;tmpfs&lt;/strong&gt;, while &lt;code&gt;~/.claude&lt;/code&gt; sits on your real disk. Two filesystems. &lt;code&gt;fs.rename()&lt;/code&gt; can't move a file across that boundary, and the installer was renaming between them.&lt;/p&gt;

&lt;p&gt;So it fired for &lt;strong&gt;any&lt;/strong&gt; plugin, whatever anything was called. The workaround was to put the temp directory on the same filesystem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TMPDIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.claude/tmp"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it's &lt;strong&gt;fixed&lt;/strong&gt; — the issue closed in February. On a current Claude Code there's nothing here for you to do.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why a wrong rule survived that long
&lt;/h3&gt;

&lt;p&gt;This is the part I want to keep.&lt;/p&gt;

&lt;p&gt;The rule was harmless. Giving the marketplace and the plugin different names costs nothing, breaks nothing, and looks tidy. So I followed it, everything worked, and &lt;strong&gt;nothing ever contradicted me.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A rule that's expensive gets challenged — sooner or later someone asks whether it's worth it. A rule that's free just accumulates. I'd been passing on a diagnosis I had never once tested, and the only reason I found out is that I sat down to write it as fact.&lt;/p&gt;

&lt;p&gt;For the record, here are the names in a working setup — and note that two of them are &lt;em&gt;supposed&lt;/em&gt; to match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .claude-plugin/marketplace.json&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;"mulmocast-plugins"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="c1"&gt;// the marketplace&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"plugins"&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;"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;"mulmocast"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&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="c1"&gt;// ← must MATCH plugin.json&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="c1"&gt;// .claude-plugin/plugin.json&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;"mulmocast"&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;h2&gt;
  
  
  While we're here: there are three names, not one
&lt;/h2&gt;

&lt;p&gt;The reason the above is confusing at all is that a plugin involves &lt;strong&gt;three separate names&lt;/strong&gt;, and the docs use them in different places:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;receptron/mulmocast-claude-plugin        ← GitHub repo
        ↓
marketplace.json  "name": "mulmocast-plugins"     ← marketplace name
marketplace.json  plugins[0].name: "mulmocast"    ← plugin name
        ↓
install:  mulmocast@mulmocast-plugins
          ^^^^^^^^^ ^^^^^^^^^^^^^^^^
          plugin    marketplace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Defined in&lt;/th&gt;
&lt;th&gt;Used by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitHub repo&lt;/td&gt;
&lt;td&gt;GitHub&lt;/td&gt;
&lt;td&gt;&lt;code&gt;marketplace add&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Marketplace name&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;marketplace.json&lt;/code&gt; → &lt;code&gt;name&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;after the &lt;code&gt;@&lt;/code&gt; in &lt;code&gt;install&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plugin name&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;marketplace.json&lt;/code&gt; → &lt;code&gt;plugins[].name&lt;/code&gt;, and the plugin's own &lt;code&gt;plugin.json&lt;/code&gt; → &lt;code&gt;name&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;before the &lt;code&gt;@&lt;/code&gt; in &lt;code&gt;install&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The repo name is used &lt;strong&gt;once&lt;/strong&gt; in this sequence, when registering the marketplace. After that you work from the names inside the JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude plugin marketplace add receptron/mulmocast-claude-plugin   &lt;span class="c"&gt;# repo name&lt;/span&gt;
claude plugin &lt;span class="nb"&gt;install &lt;/span&gt;mulmocast@mulmocast-plugins                 &lt;span class="c"&gt;# plugin@marketplace&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Minimum layout. These can be two repos or one — mine is one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-plugin-repo/
  .claude-plugin/
    marketplace.json     # the catalogue
    plugin.json          # this plugin's metadata
  skills/
    my-skill/
      SKILL.md           # the actual skill
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Which distribution method to pick
&lt;/h2&gt;

&lt;p&gt;There are three, and they're not competing for the same job.&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;Reach&lt;/th&gt;
&lt;th&gt;Ships MCP servers&lt;/th&gt;
&lt;th&gt;Ships hooks&lt;/th&gt;
&lt;th&gt;Install&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Your own marketplace&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/plugin install name@market&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Official store&lt;/strong&gt; (&lt;a href="https://github.com/anthropics/claude-plugins-official" rel="noopener noreferrer"&gt;claude-plugins-official&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;Claude Code, no setup step&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/plugin install name@claude-plugins-official&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://github.com/vercel-labs/skills" rel="noopener noreferrer"&gt;Skills CLI&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cursor, Codex, OpenCode and dozens more&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx skills add owner/repo&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Roughly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code only, and you use MCP servers or hooks&lt;/strong&gt; → plugin. Start with your own marketplace, apply to the official store once it's proven.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You want it to work in Cursor, Codex and the rest too&lt;/strong&gt; → Skills CLI. It symlinks &lt;code&gt;SKILL.md&lt;/code&gt; into each agent's directory, so one repo covers all of them. You give up MCP and hooks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The columns say &lt;em&gt;ships&lt;/em&gt; deliberately. Skills CLI distributes &lt;code&gt;SKILL.md&lt;/code&gt; files; it does not install plugin manifests, so MCP servers and plugin hooks don't come with them. The target agent may well support hooks on its own.&lt;/p&gt;

&lt;p&gt;And the thing I'd actually weigh about the official store isn't discovery — it's that users skip &lt;code&gt;marketplace add&lt;/code&gt; entirely. One less step where someone gives up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the three have in common
&lt;/h2&gt;

&lt;p&gt;The first two live in the same place: &lt;strong&gt;I had only ever run this where the names happen to be right.&lt;/strong&gt; Locally the skill is &lt;code&gt;story&lt;/code&gt; and the CLI is &lt;code&gt;yarn run cli&lt;/code&gt;. Published, it's &lt;code&gt;mulmocast:story&lt;/code&gt; and &lt;code&gt;mulmocast&lt;/code&gt;. Both failures sit in that gap.&lt;/p&gt;

&lt;p&gt;The third is the same gap from the other side. I'm on macOS, so a Linux-only failure was never going to reach me — and because my workaround for it was free, no evidence was ever going to reach me either. I'd built a belief that couldn't be falsified from where I was standing.&lt;/p&gt;

&lt;p&gt;So the useful habit isn't "test the plugin" — it's &lt;strong&gt;install it the way a stranger would, on a machine that isn't yours&lt;/strong&gt;, before you tell anyone it exists.&lt;/p&gt;

&lt;p&gt;And if you're carrying a rule you've never seen fail: that isn't evidence it works. It might just be cheap.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Docs: &lt;a href="https://code.claude.com/docs/en/plugins" rel="noopener noreferrer"&gt;Claude Code plugins&lt;/a&gt; · &lt;a href="https://code.claude.com/docs/en/plugin-marketplaces" rel="noopener noreferrer"&gt;Marketplaces&lt;/a&gt; · &lt;a href="https://agentskills.io/specification" rel="noopener noreferrer"&gt;Agent Skills spec&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>opensource</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I stopped reviewing my own code. Here's what had to be true first.</title>
      <dc:creator>Isamu Arimoto</dc:creator>
      <pubDate>Sat, 01 Aug 2026 06:38:10 +0000</pubDate>
      <link>https://dev.to/isamu/i-stopped-reviewing-my-own-code-heres-what-had-to-be-true-first-4nh0</link>
      <guid>https://dev.to/isamu/i-stopped-reviewing-my-own-code-heres-what-had-to-be-true-first-4nh0</guid>
      <description>&lt;p&gt;Most days now, I merge pull requests without reading the diff.&lt;/p&gt;

&lt;p&gt;That sentence used to describe someone I would not have hired. So let me be precise about what changed, because it isn't confidence and it isn't recklessness. It's that &lt;strong&gt;I moved the things review was catching to somewhere that catches them earlier.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's the honest version of how that happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem was arithmetic, not philosophy
&lt;/h2&gt;

&lt;p&gt;I run several coding agents in parallel. That produces more diff per day than I can read. Not "more than I feel like reading" — genuinely more than fits in a working day.&lt;/p&gt;

&lt;p&gt;When that happens you have exactly two options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate less, so it fits what you can read.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Make it safe to not read.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I picked the second one. Not because I'm brave, but because option 1 means throwing away the reason I set this up.&lt;/p&gt;

&lt;p&gt;The uncomfortable part: option 2 is not a mindset. It's a list of specific things that have to be true. Here's mine.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The rules live in a file, not in review comments
&lt;/h2&gt;

&lt;p&gt;Every code review I've ever done, the majority of my comments were mechanical. &lt;em&gt;This function is too long. This nesting is too deep. Why is this &lt;code&gt;any&lt;/code&gt;?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Machines can say all of that. So I made them say it, as &lt;strong&gt;errors&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;max-lines-per-function&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&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;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;skipBlankLines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="nx"&gt;complexity&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="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;max-depth&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;max-nested-callbacks&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus &lt;code&gt;eslint-plugin-sonarjs&lt;/code&gt; with cognitive-complexity as an error, and &lt;code&gt;@typescript-eslint&lt;/code&gt;'s strict preset — &lt;code&gt;any&lt;/code&gt; banned, non-null assertions banned.&lt;/p&gt;

&lt;p&gt;Nothing here is novel. What's different is the next part.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The rules are stricter than a human team would tolerate
&lt;/h2&gt;

&lt;p&gt;This is the part I find genuinely interesting.&lt;/p&gt;

&lt;p&gt;If you put those thresholds on a human team, you get a PR relaxing them within a week. Not because engineers are lazy — because "this function is 63 lines and splitting it makes it worse" is &lt;em&gt;sometimes true&lt;/em&gt;, and arguing about it every time is exhausting.&lt;/p&gt;

&lt;p&gt;Lint strictness has always been a trade-off between &lt;strong&gt;machine correctness&lt;/strong&gt; and &lt;strong&gt;human patience&lt;/strong&gt;. And loosening the rules was never really a technical decision. It was a social one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An agent doesn't get annoyed.&lt;/strong&gt; It reads the rule, splits the function, moves on. It has no opinion about being told to do it again tomorrow.&lt;/p&gt;

&lt;p&gt;So the social cost went to zero, and once that happens the trade-off only tips one way. I turned everything up until it hurt, and nobody complained, because nobody was there to complain.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Exceptions live in the config with a reason, never inline
&lt;/h2&gt;

&lt;p&gt;The moment rules get strict, real exceptions appear. If you allow &lt;code&gt;// eslint-disable-next-line&lt;/code&gt;, the rules are dead within a month — that comment is invisible in review and permanent in practice.&lt;/p&gt;

&lt;p&gt;So exceptions go in the config file, &lt;strong&gt;one line per reason&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;files&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="s2"&gt;src/components/Sidebar.vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// @keyframes — the "thinking" spinner ring&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src/components/GuiPanel.vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// `.frame + .frame` sibling-combinator spacing&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src/components/FilesOverlay.vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// :deep into CodeMirror's injected root&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="nx"&gt;rules&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="s2"&gt;vue/no-restricted-block&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="s2"&gt;off&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every entry says &lt;em&gt;why&lt;/em&gt;. And the comment above the block says: delete the entry when the reason goes away.&lt;/p&gt;

&lt;p&gt;The difference is visibility. An inline disable is invisible. A growing allowlist in a config file is a &lt;strong&gt;thing you can look at and be embarrassed by&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The rules themselves are pure functions, and they're tested
&lt;/h2&gt;

&lt;p&gt;This is the part most setups skip.&lt;/p&gt;

&lt;p&gt;If a rule decides something — which file extension goes where, how a path is normalised, what counts as a valid session id — that decision is &lt;em&gt;code&lt;/em&gt;, and code that is only exercised through a UI is code nobody tests.&lt;/p&gt;

&lt;p&gt;So decisions get pulled out into pure functions in a shared module, and those functions get tested against the awkward cases: empty, null, boundary, wrong-cased, wrong-platform. That project currently has a few thousand test cases, and the vast majority are testing small pure functions rather than flows.&lt;/p&gt;

&lt;p&gt;The point is not the count. The point is: &lt;strong&gt;when the rule is a pure function, the rule can be tested. When it's embedded in a component, it can only be reviewed.&lt;/strong&gt; And I stopped reviewing.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. CI runs on the OS your users have, not the one you have
&lt;/h2&gt;

&lt;p&gt;Our whole team is on macOS. Our users are not.&lt;/p&gt;

&lt;p&gt;So CI runs Linux and macOS on every PR, and Windows on a nightly schedule (it's slow, and daily is enough to catch drift).&lt;/p&gt;

&lt;p&gt;This one paid for itself immediately, and not in the way I expected. &lt;strong&gt;It's the only environment where I can reproduce a Windows bug report at all.&lt;/strong&gt; Before, a Windows issue meant asking the reporter to test my guesses. Now I push a branch.&lt;/p&gt;

&lt;p&gt;We also write Windows-specific test cases deliberately — path separators, &lt;code&gt;realpathSync&lt;/code&gt; behaviour, &lt;code&gt;fs.watch&lt;/code&gt; differences — so that fixing one doesn't quietly break the others.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Something else reads the code — and it isn't the thing that wrote it
&lt;/h2&gt;

&lt;p&gt;Claude Code writes. Then &lt;strong&gt;Codex reviews&lt;/strong&gt;, and CodeRabbit reviews, and the loop runs until they stop objecting.&lt;/p&gt;

&lt;p&gt;The mechanism that matters here isn't "AI review is good." It's that &lt;strong&gt;the writer and the reader are different models.&lt;/strong&gt; A model reviewing its own output shares its own blind spots. Two different ones don't, mostly.&lt;/p&gt;

&lt;p&gt;This is the closest thing to a replacement for what I stopped doing. It isn't as good as a careful human reviewer. It is &lt;em&gt;much&lt;/em&gt; better than a tired human reviewer at 11pm on the fortieth PR of the day, which was the realistic alternative.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I still look at
&lt;/h2&gt;

&lt;p&gt;I want to be honest about the boundary, because "I don't review anything" would be a lie:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;UI changes.&lt;/strong&gt; Nothing in the list above can tell me a layout is ugly or a flow is confusing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anything I couldn't verify by running it.&lt;/strong&gt; If the change is about behaviour under conditions CI doesn't reproduce, I go look.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anything touching auth, permissions, or data loss.&lt;/strong&gt; The blast radius is wrong for automation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything else, I let through on green.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest cost
&lt;/h2&gt;

&lt;p&gt;Three things I'd want to know if I were reading this skeptically:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It front-loads a lot of work.&lt;/strong&gt; None of the six items above is free. If you set up two of them and stop, you have strictness without a safety net, which is worse than neither.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It only works if the stack is uniform.&lt;/strong&gt; The reason my per-repo config files are nearly empty is that every project uses the same language, the same test runner, the same CI shape. If your repos disagree with each other, you'll be writing the same rules over and over. Fix that first; it's cheaper.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed is not correctness.&lt;/strong&gt; Things do get shipped fast and fixed fast. What this setup buys is not "no bugs" — it's that &lt;strong&gt;the bugs that survive are the ones review wouldn't have caught either.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing I actually took away
&lt;/h2&gt;

&lt;p&gt;I set all this up to save time, and it did. But that isn't the interesting part.&lt;/p&gt;

&lt;p&gt;The interesting part is that lint strictness, test coverage, CI breadth — the whole category of "engineering discipline we know we should do but don't" — was &lt;strong&gt;never really blocked on knowing better.&lt;/strong&gt; It was blocked on how much friction a human team will absorb before it starts negotiating.&lt;/p&gt;

&lt;p&gt;That constraint just got removed. Not gradually. It's gone.&lt;/p&gt;

&lt;p&gt;I don't think most of us have updated for that yet.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Everything above runs in the open: &lt;a href="https://github.com/isamu/claude" rel="noopener noreferrer"&gt;my global config&lt;/a&gt;&lt;br&gt;
  and the &lt;a href="https://github.com/receptron/mulmoterminal" rel="noopener noreferrer"&gt;project it runs on&lt;/a&gt;, both MIT.&lt;br&gt;
  Copy whatever's useful.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>AI Changed the Bottleneck of Software Development</title>
      <dc:creator>Isamu Arimoto</dc:creator>
      <pubDate>Thu, 23 Jul 2026 08:27:52 +0000</pubDate>
      <link>https://dev.to/isamu/ai-changed-the-bottleneck-of-software-development-58og</link>
      <guid>https://dev.to/isamu/ai-changed-the-bottleneck-of-software-development-58og</guid>
      <description>&lt;p&gt;For decades, software engineering was constrained by one thing: writing code.&lt;/p&gt;

&lt;p&gt;Today, that bottleneck is disappearing.&lt;/p&gt;

&lt;p&gt;With tools like Claude Code, Codex, and other coding agents, generating thousands of lines of code is almost free. The new constraint is no longer how fast we can write code—it's how fast we can keep a codebase healthy.&lt;/p&gt;

&lt;p&gt;That is a fundamentally different problem.&lt;/p&gt;

&lt;p&gt;AI Doesn't Create Bad Code. It Creates Code Too Easily.&lt;/p&gt;

&lt;p&gt;Modern coding agents are remarkably good at producing working code.&lt;/p&gt;

&lt;p&gt;But they also tend to:&lt;/p&gt;

&lt;p&gt;create another helper function instead of reusing an existing one&lt;br&gt;
leave dead utilities after refactoring&lt;br&gt;
grow functions to hundreds of lines&lt;br&gt;
fall back to any when type inference becomes difficult&lt;br&gt;
introduce subtle architectural violations that are hard to notice in code review&lt;/p&gt;

&lt;p&gt;None of these are catastrophic individually.&lt;/p&gt;

&lt;p&gt;Together, they slowly increase technical debt.&lt;/p&gt;

&lt;p&gt;The challenge isn't intelligence—it's scale.&lt;/p&gt;

&lt;p&gt;When an AI can generate code 10× faster than a human, it can also generate technical debt 10× faster.&lt;/p&gt;

&lt;p&gt;Human Code Review Doesn't Scale Either&lt;/p&gt;

&lt;p&gt;Even experienced reviewers can't remember a 300,000-line codebase.&lt;/p&gt;

&lt;p&gt;Suppose a pull request adds another truncate() function.&lt;/p&gt;

&lt;p&gt;Is there already one somewhere else?&lt;/p&gt;

&lt;p&gt;Maybe.&lt;/p&gt;

&lt;p&gt;Maybe there are six.&lt;/p&gt;

&lt;p&gt;No reviewer can reliably answer that from memory.&lt;/p&gt;

&lt;p&gt;The same applies to unused exports, circular dependencies, or architectural boundaries.&lt;/p&gt;

&lt;p&gt;These aren't problems humans should spend their time searching for.&lt;/p&gt;

&lt;p&gt;They're mechanical problems.&lt;/p&gt;

&lt;p&gt;Let Machines Enforce Code Hygiene&lt;/p&gt;

&lt;p&gt;The obvious solution is to automate the boring parts.&lt;/p&gt;

&lt;p&gt;Our development workflow combines several layers of static analysis:&lt;/p&gt;

&lt;p&gt;ESLint for complexity and coding standards&lt;br&gt;
SonarJS for maintainability and type safety&lt;br&gt;
jscpd for duplicated code&lt;br&gt;
Knip for dead code detection&lt;/p&gt;

&lt;p&gt;Each tool catches a different class of problems.&lt;/p&gt;

&lt;p&gt;No single tool is sufficient.&lt;/p&gt;

&lt;p&gt;Together, they continuously protect the codebase while developers—and AI agents—focus on solving actual problems.&lt;/p&gt;

&lt;p&gt;DRY Matters Even More in the AI Era&lt;/p&gt;

&lt;p&gt;Many people think DRY ("Don't Repeat Yourself") is mainly about maintainability.&lt;/p&gt;

&lt;p&gt;Today, it has another benefit.&lt;/p&gt;

&lt;p&gt;AI agents consume context.&lt;/p&gt;

&lt;p&gt;Six duplicated implementations don't just increase maintenance cost—they also increase token consumption.&lt;/p&gt;

&lt;p&gt;Large functions require more context.&lt;/p&gt;

&lt;p&gt;Duplicate utilities require more searching.&lt;/p&gt;

&lt;p&gt;Dead code increases noise.&lt;/p&gt;

&lt;p&gt;A cleaner codebase is also a cheaper codebase for AI.&lt;/p&gt;

&lt;p&gt;Good architecture now directly improves AI efficiency.&lt;/p&gt;

&lt;p&gt;CI Should Prevent Regression, Not Punish History&lt;/p&gt;

&lt;p&gt;One lesson we've learned is that not every static analysis tool should block a pull request.&lt;/p&gt;

&lt;p&gt;Some checks can accurately identify new problems.&lt;/p&gt;

&lt;p&gt;Those belong in CI.&lt;/p&gt;

&lt;p&gt;Others can only report the current state of the repository.&lt;/p&gt;

&lt;p&gt;Those should provide visibility rather than block development.&lt;/p&gt;

&lt;p&gt;The goal isn't to eliminate all technical debt overnight.&lt;/p&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;p&gt;Never create new debt faster than you remove the old one.&lt;/p&gt;

&lt;p&gt;The New Engineering Skill&lt;/p&gt;

&lt;p&gt;As AI continues to improve, writing code becomes less valuable as a competitive advantage.&lt;/p&gt;

&lt;p&gt;The differentiator shifts toward something else:&lt;/p&gt;

&lt;p&gt;Designing systems that remain understandable, modular, and maintainable—even when most of the code is written by machines.&lt;/p&gt;

&lt;p&gt;Software engineering is becoming less about typing code and more about managing the quality of an ever-growing codebase.&lt;/p&gt;

&lt;p&gt;The bottleneck has moved.&lt;/p&gt;

&lt;p&gt;The teams that recognize this early will build software faster—not because their AI writes more code, but because their systems stay clean enough for AI to keep writing it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
    </item>
  </channel>
</rss>
