<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>With a Twist</title>
  <subtitle>Software Development</subtitle>
  <id>https://withatwist.dev/</id>
  <link href="https://withatwist.dev/"/>
  <link href="https://withatwist.dev/feed.xml" rel="self"/>
  <updated>2026-03-19T00:00:00+00:00</updated>
  <author>
    <name>Mauro Otonelli, Sebastian Armano, Tute Costa</name>
  </author>
  <entry>
    <title>An agent chat turned into a skill, and then it QA'd itself</title>
    <link rel="alternate" href="https://withatwist.dev/an-agent-chat-that-turned-into-skill-that-tests-itself.html"/>
    <id>https://withatwist.dev/an-agent-chat-that-turned-into-skill-that-tests-itself.html</id>
    <published>2026-03-19T00:00:00+00:00</published>
    <updated>2026-03-20T01:09:51+00:00</updated>
    <author>
      <name>Tute Costa</name>
    </author>
    <summary type="html">&lt;p&gt;I had a simple &lt;code&gt;index.html&lt;/code&gt; page, a marketing site for a client in Spanish.
They asked an English version too, so I opened the chat to Claude’s Opus 4.5 model and asked it to create a near-identical &lt;code&gt;en/index.html&lt;/code&gt; with content translated.
Easy peasy.&lt;/p&gt;

&lt;p&gt;I opened both files side by side and skimmed, they looked the same with lines in different languages.
But I’m used to testing changes, and agents find things to fix before I start more careful checking.
While I had this thought, the client recalled they’d need a version in Portuguese as well.
“Sure”.&lt;/p&gt;

&lt;p&gt;Now, would they synchronize updates in Spanish to the other two languages?
Content and design will surely drift.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;</summary>
    <content type="html">&lt;p&gt;I had a simple &lt;code&gt;index.html&lt;/code&gt; page, a marketing site for a client in Spanish.
They asked an English version too, so I opened the chat to Claude&amp;rsquo;s Opus 4.5 model and asked it to create a near-identical &lt;code&gt;en/index.html&lt;/code&gt; with content translated.
Easy peasy.&lt;/p&gt;

&lt;p&gt;I opened both files side by side and skimmed, they looked the same with lines in different languages.
But I&amp;rsquo;m used to testing changes, and agents find things to fix before I start more careful checking.
While I had this thought, the client recalled they&amp;rsquo;d need a version in Portuguese as well.
&amp;ldquo;Sure&amp;rdquo;.&lt;/p&gt;

&lt;p&gt;Now, would they synchronize updates in Spanish to the other two languages?
Content and design will surely drift.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;h2&gt;The solution&lt;/h2&gt;

&lt;p&gt;I used &lt;a href="https://github.com/anthropics/skills/tree/main/skills/skill-creator"&gt;Claude&amp;rsquo;s &lt;code&gt;/skill-creator&lt;/code&gt;&lt;/a&gt;
to &lt;strong&gt;build reusable instructions to check parity across all languages&lt;/strong&gt;. My simple prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Create a skill to check that the index files read well in all languages (Spanish and English, Portuguese upcoming).
Check translations match, no page has content that the other does not, and that markup and UI looks and behaves the same.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent started writing a Python script to check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DOM structure is identical after normalizing expected differences (like the language switcher)&lt;/li&gt;
&lt;li&gt;Texts that should be translated aren&amp;rsquo;t identical across files&lt;/li&gt;
&lt;li&gt;Same images, same order&lt;/li&gt;
&lt;li&gt;Same stylesheets&lt;/li&gt;
&lt;li&gt;Internal consistency (nav anchors to section IDs, &lt;code&gt;&amp;lt;html lang&amp;gt;&lt;/code&gt; attribute)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I expected hand-wavy, maybe helpful browser-based checks. It wrote a deterministic Python script.&lt;/p&gt;

&lt;h3&gt;A noisy first run&lt;/h3&gt;

&lt;p&gt;I observed its thought process; it started flagging dozens of mismatches.
&lt;strong&gt;Most of them cascaded from a single root cause&lt;/strong&gt;: an extra closing &lt;code&gt;&amp;lt;/div&amp;gt;&lt;/code&gt; in the English file
threw off every structural comparison after it. The output was technically correct, but not helpful as a checklist.&lt;/p&gt;

&lt;p&gt;It iterated: it identified the root divergence pattern, filtered expected differences
(the language switcher, text that&amp;rsquo;s legitimately identical across languages like proper nouns or acronyms),
and re-ran until the output was clean.
It also flagged actual issues it had found while building its skill:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The false positives disappeared. Now only the 2 real problems remain.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;Two real bugs I&amp;rsquo;d missed&lt;/h3&gt;

&lt;p&gt;The extra &lt;code&gt;&amp;lt;/div&amp;gt;&lt;/code&gt; was a copy-paste error (that didn&amp;rsquo;t break the UI and I hadn&amp;rsquo;t noticed).
And an image at the footer had an old path I hadn&amp;rsquo;t finished migrating.
&lt;strong&gt;The skill caught them while testing itself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/2026-03-19.png" alt="Claude Opus 4.5 finds two bugs while creating the new skill" /&gt;&lt;/p&gt;

&lt;h2&gt;Let&amp;rsquo;s add Portuguese now&lt;/h2&gt;

&lt;p&gt;When I added &lt;code&gt;pt/index.html&lt;/code&gt;, the parity script immediately had a new problem: Spanish and Portuguese share too much vocabulary.
&amp;ldquo;Engenharia&amp;rdquo; and &amp;ldquo;Ingeniería&amp;rdquo; are different enough, but dozens of short terms like proper nouns or abbreviations are identical.
The script was flagging legitimate cognates as missing translations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opus taught the script about language proximity:&lt;/strong&gt; it introduced a &lt;code&gt;CLOSE_LANG_PAIRS&lt;/code&gt; to relate Spanish and Portuguese,
identical text under 30 characters is assumed to be a legitimate cognate and skipped.
For distant pairs (like Spanish and English), any identical text is still suspicious.&lt;/p&gt;

&lt;p&gt;That was creative. A human may get there after a while.
The agent got there in a chat that turned into an effective, reusable skill.&lt;/p&gt;

&lt;h2&gt;Closing thoughts&lt;/h2&gt;

&lt;p&gt;Internationalizing projects used to be complicated. I asked for English and then Portuguese versions, and
I got a system where adding a new language is a tool call and a proof read away.
&lt;strong&gt;A system specific to this one project&lt;/strong&gt;, with its single &lt;code&gt;index.html&lt;/code&gt; copied over
in language subdirectories, &lt;strong&gt;because it&amp;rsquo;s simple, and with LLMs it&amp;rsquo;s now easy to maintain&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Testing the skill on real files caught bugs in the HTML.
Adding a third language taught the skill to handle distant and proximate languages.
Each step improved either the thing being checked or the thing doing the checking.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Claude, tell me what needs my attention today</title>
    <link rel="alternate" href="https://withatwist.dev/claude-tell-me-what-needs-my-attention-today.html"/>
    <id>https://withatwist.dev/claude-tell-me-what-needs-my-attention-today.html</id>
    <published>2025-12-23T00:00:00+00:00</published>
    <updated>2026-03-20T01:09:51+00:00</updated>
    <author>
      <name>Tute Costa</name>
    </author>
    <summary type="html">&lt;p&gt;Each morning I open four tabs: Calendar, Gmail, Jira, and Obsidian. I read all, discard low priority emails or calendar events, and &lt;strong&gt;start building a mental picture of what I’ll do that day, and when exactly&lt;/strong&gt;.
For each piece of data I decide whether it’s time sensitive or not, whether I need to prepare beforehand, and whether I should tackle it later (or never).
After warming up my brain, I kick off the actual work.&lt;/p&gt;

&lt;p&gt;I decided &lt;strong&gt;an LLM should do that pre-work instead of my well-rested brain&lt;/strong&gt;. To build such automation I’d practice Claude Code subagents and local MCP servers setup&lt;/p&gt;</summary>
    <content type="html">&lt;p&gt;Each morning I open four tabs: Calendar, Gmail, Jira, and Obsidian. I read all, discard low priority emails or calendar events, and &lt;strong&gt;start building a mental picture of what I&amp;rsquo;ll do that day, and when exactly&lt;/strong&gt;.
For each piece of data I decide whether it&amp;rsquo;s time sensitive or not, whether I need to prepare beforehand, and whether I should tackle it later (or never).
After warming up my brain, I kick off the actual work.&lt;/p&gt;

&lt;p&gt;I decided &lt;strong&gt;an LLM should do that pre-work instead of my well-rested brain&lt;/strong&gt;. To build such automation I&amp;rsquo;d practice Claude Code subagents and local MCP servers setup,
a good exercise for my new startup, &lt;a href="https://www.railspilot.ai/"&gt;RailsPilot.ai&lt;/a&gt;. So I started creating my &lt;code&gt;/today&lt;/code&gt; Claude Code command.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/today&lt;/code&gt; fetches information scattered accross mails, calendar, task manager, and notes, and compiles them into a prioritized task list for the day.
It saves the output to a plain text file like &lt;code&gt;daily_notes/2025-12-23.txt&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;It&amp;rsquo;s funny to see Claude properly weighing what&amp;rsquo;s worthy of my attention from disparate sources according to natural language explanations, while it juggles different commands to decide what day is today.
&lt;strong&gt;Discovering what&amp;rsquo;s cheap vs challening with LLMs is what I seek, and is also funny to me&lt;/strong&gt;.
December 22nd, 2025 is NOT Sunday, Claude, it&amp;rsquo;s Monday. Wise up!&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/2025-12-23-claude.png" alt="Claude Code organizing my daily tasks" /&gt;&lt;/p&gt;

&lt;p&gt;It first works to define what day is today, then launches three parallel subagents, each responsible for fetching, processing and normalizing data from one source:&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/2025-12-23.png" alt="/today command's flowchart: spawns three parallel subagents for Calendar, Gmail, and Jira" /&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;em&gt;Jira&lt;/em&gt; subagent fetches assigned issues and checks for QA comments that need my attention: my highest priority these end-of-year days.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;Calendar&lt;/em&gt; subagent retrieves today&amp;rsquo;s events and applies priority logic (marks certain family events as low priority reminders, whereas time-sensitive meetings are marked as high priority and with an action item to prepare 15 minutes beforehand).&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;Gmail&lt;/em&gt; subagent reads my inbox, detects newsletters to deprioritize and defines suggested action items for the rest.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture is designed for efficiency: &lt;strong&gt;raw API responses are contained within each subagent&amp;rsquo;s context window and discarded after processing&lt;/strong&gt;, so only compact JSON summaries flow back to the main agent.
This keeps the context clean and &lt;strong&gt;allows each source to fail independently&lt;/strong&gt; without breaking the entire command.
It will also let me enable only relevant MCPs per subagent, another context window optimization I didn&amp;rsquo;t yet need.&lt;/p&gt;

&lt;p&gt;Each subagent normalizes its data into a common task schema with fields like &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;source&lt;/code&gt;, &lt;code&gt;priority&lt;/code&gt;, &lt;code&gt;due date&lt;/code&gt;, &lt;code&gt;URL&lt;/code&gt;, and &lt;code&gt;action item&lt;/code&gt;. Low-priority items include a reason why. &lt;strong&gt;This normalization allows disparate data pieces to be compared and sorted&lt;/strong&gt;. When all subagents return their results to the orchestrator agent, it merges everything into a single list sorted by priority and time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The final output is plain text designed for quick scanning top to bottom&lt;/strong&gt;, with key details, URL and action item after each title.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;Daily Tasks - December 23, 2025
==============================

• [DEV-450] Enable PWA On Select Pages
  - Status: IN QA
  - Action: Respond to QA comments from David regarding errors on refresh
  - URL: https://client.atlassian.net/browse/DEV-450

• Your Documents are now ready for Reservation ID: 123456
  - From: Provider &amp;lt;^DONOTREPLY@provider.com&amp;gt;
  - Action: download and print documents, complete forms
  - URL: https://mail.google.com/mail/u/0/#inbox/123456789

• [DEV-430] Landing Page N+1s
  - Status: IN QA
  - Action: No QA feedback yet
  - URL: https://client.atlassian.net/browse/DEV-430

• Kids visit Granny - 3:00 PM to 7:00 PM
  - Note: Family reminder
  - URL: https://www.google.com/calendar/event?eid=eideideideideideideid

---
Generated at 2025-12-23 09:30:00
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And, instead of sifting through tabs, I can go from cup of coffee straight to my top priority for the day.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>How I use AI agents to code new features</title>
    <link rel="alternate" href="https://withatwist.dev/how-i-use-ai-agents-to-code-new-features.html"/>
    <id>https://withatwist.dev/how-i-use-ai-agents-to-code-new-features.html</id>
    <published>2025-02-20T00:00:00+00:00</published>
    <updated>2026-03-20T01:09:51+00:00</updated>
    <author>
      <name>Tute Costa</name>
    </author>
    <summary type="html">&lt;p&gt;I wrote about how Cursor&amp;rsquo;s AI agent helped me build a high quality new feature in
Rails, without much &amp;ldquo;prompt engineering&amp;rdquo; getting in the way.&lt;/p&gt;

&lt;p&gt;You can find it in my employer&amp;rsquo;s blog:
&lt;a href="https://buoy.blog/2025/02/11/how-i-use-ai-agents-to-code-new-features.html"&gt;&amp;ldquo;How I use AI agents to code new features&amp;rdquo;&lt;/a&gt;.&lt;/p&gt;
</summary>
    <content type="html">&lt;p&gt;I wrote about how Cursor&amp;rsquo;s AI agent helped me build a high quality new feature in
Rails, without much &amp;ldquo;prompt engineering&amp;rdquo; getting in the way.&lt;/p&gt;

&lt;p&gt;You can find it in my employer&amp;rsquo;s blog:
&lt;a href="https://buoy.blog/2025/02/11/how-i-use-ai-agents-to-code-new-features.html"&gt;&amp;ldquo;How I use AI agents to code new features&amp;rdquo;&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Writing Architecture Documents (for developers)</title>
    <link rel="alternate" href="https://withatwist.dev/writing-architecture-documents-for-developers.html"/>
    <id>https://withatwist.dev/writing-architecture-documents-for-developers.html</id>
    <published>2022-03-31T00:00:00+00:00</published>
    <updated>2026-03-20T01:09:51+00:00</updated>
    <author>
      <name>Tute Costa</name>
    </author>
    <summary type="html">&lt;p&gt;Our colleague Martin was about to start working on a small-scale feature, an addition to a module
that wasn’t open for extension and couldn’t easily support it.&lt;/p&gt;

&lt;p&gt;He could &lt;strong&gt;shoehorn his use case on top of existing architecture without affecting others much
or lay a more robust foundation&lt;/strong&gt; over which he’d implement his use case. This could also help fix long-standing bugs and enable the implementation of new features that we foresee in our next two years (but would be hard to add now).
In other words, “make the change easy, then make the easy change”.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;</summary>
    <content type="html">&lt;p&gt;Our colleague Martin was about to start working on a small-scale feature, an addition to a module
that wasn’t open for extension and couldn’t easily support it.&lt;/p&gt;

&lt;p&gt;He could &lt;strong&gt;shoehorn his use case on top of existing architecture without affecting others much
or lay a more robust foundation&lt;/strong&gt; over which he&amp;rsquo;d implement his use case. This could also help fix long-standing bugs and enable the implementation of new features that we foresee in our next two years (but would be hard to add now).
In other words, &amp;ldquo;make the change easy, then make the easy change&amp;rdquo;.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;We are a 20-people team organized into three pods (each
with a Product Manager, a QA tester, and about three developers), designers, and leadership roles.
The three pod structure helped us &lt;strong&gt;work without needing to have the context of the whole application&lt;/strong&gt; in mind
but made us &lt;strong&gt;need to sync up whenever a decision would affect others&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Martin postponed his solution in favor of determining a better architecture.
He needed to write up a document to share ideas with everyone,
ensuring we covered every pod’s needs and addressed the comparatively minor feature that led to these conversations.&lt;/p&gt;

&lt;p&gt;Martin wanted the document to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;be &lt;strong&gt;succinct&lt;/strong&gt;, so everybody reads it, can share feedback, and signs off on
&lt;strong&gt;–while also adding much detail&lt;/strong&gt; to ensure no scenario (current or future) is left unattended&lt;/li&gt;
&lt;li&gt;use &lt;strong&gt;general language&lt;/strong&gt; so his Product Manager can follow it
&lt;strong&gt;while also using technical jargon&lt;/strong&gt;, so his CTO will know precisely where we are heading&lt;/li&gt;
&lt;li&gt;be &lt;strong&gt;quick&lt;/strong&gt;, so he can get going with his solution
&lt;strong&gt;while also letting the document sit for days&lt;/strong&gt; so
people in different time zones share their feedback&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How could he write the document with such competing goals? Martin and I paired on it many times,
took notes, shared with the team, and wrote what worked well for us.&lt;/p&gt;

&lt;h1&gt;Definitions&lt;/h1&gt;

&lt;p&gt;First, Martin defined an objective, an audience, and a desirable trait for the document:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Objective&lt;/strong&gt;: Efficiently communicate the problems and goals for the audience
to discuss and support our decision, ideally without the need for meetings.
Many people will read it many times, so we’ll take the time to make it clear and succinct.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audience&lt;/strong&gt;: For this case, our Director of Engineering. A single person, which in turn
helps define the level of detail, tone, and deadline for the document.
Interested developers and technically inclined Product Managers should be able to follow along a document intended for our Director
(or know whom to ask for clarifications).&lt;br&gt;
Even if more people are deciders for the solution, it will help the writer to have
a single person in mind to whom he’s writing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Desirable&lt;/strong&gt;: Discuss at least two alternative solutions for the problem,
outlining their pros and cons.
What would the solution look like if we had all the time in the world?
Or double our team size? Or only half of our team size?
What if we needed to get something out in two weeks?&lt;/p&gt;

&lt;h1&gt;The document’s structure&lt;/h1&gt;

&lt;p&gt;Most developers at Epion have much less experience writing documents than writing code. Martin got a case of blank page block.
How could he put in a couple of sheets of paper everything we discussed over lunch and on several calls,
looking both at the short and long term?
There were still open questions and even unknown unknowns!&lt;/p&gt;

&lt;p&gt;I suggested starting from a template defining a structure.
&lt;strong&gt;How should an Architecture Document look when skimming from afar?&lt;/strong&gt;
We wrote that down with some generic content that we’d iterate on for the feature and project at hand.&lt;/p&gt;

&lt;p&gt;The blueprint with dummy content looked like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;h1&gt;Bariloche (a short &amp;ldquo;catchy&amp;rdquo; name)&lt;/h1&gt;

&lt;h2&gt;High-level overview&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What&lt;/strong&gt; we want (in order of priority):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A great cost/benefit ratio (weigh up requirements and available resources)&lt;/li&gt;
&lt;li&gt;Seize a good risk/value ratio (some risk is acceptable for great value; what’s our aim and tolerance?)&lt;/li&gt;
&lt;li&gt;Make our teams happier (what specific teams, and what can we improve for them?)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Not a fourth! That’s enough; we shall define the top ones only.
Also ok: &amp;ldquo;we don’t want to&amp;quot;s: we don’t want to continue seeing a current architecture problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who&lt;/strong&gt; will participate in this decision, who will approve it, and who are responsible for executing it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When&lt;/strong&gt;: a timeline and the &amp;quot;why&amp;rdquo; behind each deadline.&lt;/p&gt;

&lt;p&gt;Top-down overview. We&amp;rsquo;ll achieve this by doing first some work that makes our pods
and their stakeholders happy (the feature at the start of this story).
We&amp;rsquo;ll also lay down foundations for our new architecture, enabling
the fixes for long-standing bugs and paving the road for features on our horizon.&lt;/p&gt;

&lt;h2&gt;Alternative A&lt;/h2&gt;

&lt;h3&gt;Description&lt;/h3&gt;

&lt;p&gt;Detailed description, supported with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ERD/class diagrams&lt;/li&gt;
&lt;li&gt;Database migrations (maybe a link, but nice to see them verbatim here to see not only entities but also their attributes and types)&lt;/li&gt;
&lt;li&gt;Implementation notes&lt;/li&gt;
&lt;li&gt;Use cases and sample data (could be a link to a GitHub spike PR, a linked document, or a block of code within).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It&amp;rsquo;s not a solution to apply as-is,
but detailed enough that developers can “run it” in their minds and
provide more specific feedback, saving time in future code reviews.&lt;/p&gt;

&lt;p&gt;List of features enabled by the design and problems prevented by it.&lt;/p&gt;

&lt;h2&gt;Alternative B&lt;/h2&gt;

&lt;h3&gt;Description&lt;/h3&gt;

&lt;p&gt;&amp;hellip;&lt;/p&gt;

&lt;h2&gt;Comparison&lt;/h2&gt;

&lt;p&gt;Pros and Cons of considered alternatives.&lt;/p&gt;

&lt;h2&gt;Timeline&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;First Quarter&lt;/strong&gt;: We&amp;rsquo;ll do the work that triggered this conversation. Pod and PM will be happy and timely get what they want.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second Quarter&lt;/strong&gt;: Developers will have a better architecture to more easily implement future work in our pipeline, such as A, B, and C.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third Quarter&lt;/strong&gt;: We&amp;rsquo;ll delete legacy implementations and run 100% on the design we want.&lt;/p&gt;

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

&lt;p&gt;Described objectives will be met (expand this).
We can’t identify additional risks (consider running &lt;a href="https://medium.com/paypal-tech/pre-mortem-technically-working-backwards-1724eafbba02"&gt;&amp;ldquo;pre-mortems&amp;rdquo;&lt;/a&gt; to find more).
We’ll be in a better place to implement future work without neglecting our short-term commitments.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;Final thoughts&lt;/h1&gt;

&lt;p&gt;One goal is to &lt;strong&gt;avoid discussions until we have a mature draft with concrete alternatives&lt;/strong&gt;.
Discussions expand the space of possibilities (&amp;ldquo;design by committee&amp;rdquo;), while writing reduces it to its most essential components.
Consensus means no ownership. But we do want to hear everyone’s feedback before making the decision.&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/2021-10-21-homers-car.jpg" alt="Design by committee project" /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In an Architecture Document we should use a tone different from what we are used to during code reviews&lt;/strong&gt;:
in code review we tend to ask rather than prescribe, use open-ended questions, and ask for early feedback.
We&amp;rsquo;ll first avoid such early discussion, only describing a path forward. Discussions will come later.&lt;/p&gt;

&lt;p&gt;With Architecture Documents we can implement our near-term work while
aligning ourselves with the long-term vision for Epion Products.&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;&lt;small&gt;I took some ideas in this article from:&lt;/small&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://review.firstround.com/square-defangs-difficult-decisions-with-this-system-heres-how"&gt;Square’s system to make decisions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;I first read about &amp;ldquo;Pre-Mortems&amp;rdquo; in &amp;ldquo;Thinking Fast and Slow&amp;rdquo; by Daniel-Kahneman (on our Epion Book Club)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/b0rk/status/1262415197345636353"&gt;Julia Evans&amp;rsquo; zine on writing for a single person&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Mystery Guest Development Edition</title>
    <link rel="alternate" href="https://withatwist.dev/mystery-guest-development-edition.html"/>
    <id>https://withatwist.dev/mystery-guest-development-edition.html</id>
    <published>2021-05-10T00:00:00+00:00</published>
    <updated>2026-03-20T01:09:51+00:00</updated>
    <author>
      <name>Sebastian Armano</name>
    </author>
    <summary type="html">&lt;p&gt;Mystery Guest is the name of an anti-pattern that often appear in tests. In
short, it is caused by non explicitly declaring or naming a value, which
is tested over that faulty declaration.  &lt;strong&gt;The issue is not apparent at
first, as the test is created at the same time as the fixture or
factory that supports it&lt;/strong&gt;. Eventually, when a change is needed in that support
object to test something else, the change unintentionally breaks the first
test.&lt;/p&gt;

&lt;p&gt;For a test which verifies that the &lt;code&gt;full_name&lt;/code&gt; of a user is the combination of
&lt;code&gt;first_name&lt;/code&gt; and &lt;code&gt;last_name&lt;/code&gt;, those two properties need to appear explicitly on
the test setup. Even if you have &lt;code&gt;user&lt;/code&gt; factory, you are not testing the
factory, but &lt;em&gt;your own&lt;/em&gt; &lt;code&gt;user&lt;/code&gt; with &lt;code&gt;first_name&lt;/code&gt; and &lt;code&gt;last_name&lt;/code&gt;.  If the users
in your application also need, say, an address not being tested in
this example, the factory can provide that.&lt;/p&gt;

&lt;p&gt;So the question arises: &lt;strong&gt;are tests the only ones affected by this
anti-pattern?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;</summary>
    <content type="html">&lt;p&gt;Mystery Guest is the name of an anti-pattern that often appear in tests. In
short, it is caused by non explicitly declaring or naming a value, which
is tested over that faulty declaration.  &lt;strong&gt;The issue is not apparent at
first, as the test is created at the same time as the fixture or
factory that supports it&lt;/strong&gt;. Eventually, when a change is needed in that support
object to test something else, the change unintentionally breaks the first
test.&lt;/p&gt;

&lt;p&gt;For a test which verifies that the &lt;code&gt;full_name&lt;/code&gt; of a user is the combination of
&lt;code&gt;first_name&lt;/code&gt; and &lt;code&gt;last_name&lt;/code&gt;, those two properties need to appear explicitly on
the test setup. Even if you have &lt;code&gt;user&lt;/code&gt; factory, you are not testing the
factory, but &lt;em&gt;your own&lt;/em&gt; &lt;code&gt;user&lt;/code&gt; with &lt;code&gt;first_name&lt;/code&gt; and &lt;code&gt;last_name&lt;/code&gt;.  If the users
in your application also need, say, an address not being tested in
this example, the factory can provide that.&lt;/p&gt;

&lt;p&gt;So the question arises: &lt;strong&gt;are tests the only ones affected by this
anti-pattern?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Let&amp;rsquo;s see an example.&lt;/p&gt;

&lt;p&gt;In our application, we had a &lt;code&gt;CheckIn&lt;/code&gt; model and a associated &lt;code&gt;Progress&lt;/code&gt;
to record when the each step of a check-in has been completed. &lt;strong&gt;We
want to calculate, among other things, the duration of each check-in for
reporting purposes&lt;/strong&gt;. We had something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# models/check_in.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CheckIn&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_one&lt;/span&gt; &lt;span class="ss"&gt;:progress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dependent: :destroy&lt;/span&gt;

  &lt;span class="n"&gt;before_create&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;progress&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;Progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
11
12
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# models/progress.rb&lt;/span&gt;
&lt;span class="no"&gt;Class&lt;/span&gt; &lt;span class="no"&gt;Progress&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:check_in&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;duration&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;check_in_completed_at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
      &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;check_in_completed_at&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;duration&lt;/code&gt; method in &lt;code&gt;Progress&lt;/code&gt; is the interesting one. We didn&amp;rsquo;t need to
use the &lt;code&gt;CheckIn&lt;/code&gt; &lt;code&gt;created_at&lt;/code&gt; field because a &lt;code&gt;Progress&lt;/code&gt; was created at the
same time. So, to simplify and optimize, we used the &lt;code&gt;Progress&lt;/code&gt; &lt;code&gt;created_at&lt;/code&gt;
method instead. &lt;strong&gt;There is no issue whatsoever with this approach. Or is
there?&lt;/strong&gt;&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;After some time, we decided to include events happening &lt;em&gt;before&lt;/em&gt; the patient
arrives to the practice, which is before the check-in.&lt;/p&gt;

&lt;p&gt;We created a new model called &lt;code&gt;Engagement&lt;/code&gt;. As we still wanted to keep track of
steps completed before and during the check-in, we associated our &lt;code&gt;Progress&lt;/code&gt; to
the Engagement which we moved up from the &lt;code&gt;CheckIn&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# models/engagement.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Engagement&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_one&lt;/span&gt; &lt;span class="ss"&gt;:check_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dependent: :destroy&lt;/span&gt;
  &lt;span class="n"&gt;has_one&lt;/span&gt; &lt;span class="ss"&gt;:progress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dependent: :destroy&lt;/span&gt;

  &lt;span class="n"&gt;before_create&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;progress&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;Progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# models/check_in.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CheckIn&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:engagement&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
11
12
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# models/progress.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Progress&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:engagement&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;duration&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;check_in_completed_at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
      &lt;span class="n"&gt;check_in_completed_at&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You have probably already seen the problem. Progresses are now sometimes created
long before the check-in starts. In some cases, &lt;em&gt;days&lt;/em&gt; before.  And since then,
our calculations of check-in &lt;code&gt;duration&lt;/code&gt; started failing.  And that is not the
biggest problem. &lt;strong&gt;The main issue is how hard this is to detect.&lt;/strong&gt;  Tests are
green, and there&amp;rsquo;s no clear indication that the calculations are wrong until you
see the actual numbers.&lt;/p&gt;

&lt;p&gt;We thought we knew all our dependencies, but &lt;strong&gt;a mysterious guest appeared!&lt;/strong&gt;  We
assumed that progresses would always be &lt;code&gt;check_in&lt;/code&gt; progresses, but that&amp;rsquo;s not
true anymore. Old &lt;code&gt;check_in&lt;/code&gt; progresses are now &lt;code&gt;engagement&lt;/code&gt; ones.
Once again, we trusted the underlying object supporting our assumptions, but it
changed on us.&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;The fix is arguably even more interesting than the bug. As a first step, we
specify that we want &lt;code&gt;check_in.created_at&lt;/code&gt; timestamp and not the &lt;code&gt;progress&lt;/code&gt;
one. Also, as a &lt;code&gt;progress&lt;/code&gt; now is associated with an &lt;code&gt;Engagement&lt;/code&gt;, we can
change the name of the method to specify what we are talking about.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
11
12
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# models/progress.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Progress&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:engagement&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_in_duration&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;check_in_completed_at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
      &lt;span class="n"&gt;check_in_completed_at&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;check_in&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;created_at&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, method name and implementation are telling us something else. All the
references to &lt;code&gt;check_in&lt;/code&gt; indicate this method probably suffers from &amp;ldquo;feature
envy&amp;rdquo;, and should be in the &lt;code&gt;CheckIn&lt;/code&gt; object instead of here. &lt;strong&gt;In our way of
fixing an issue we found a better design for our code as well!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the final version:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# models/engagement.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Engagement&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_one&lt;/span&gt; &lt;span class="ss"&gt;:check_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dependent: :destroy&lt;/span&gt;
  &lt;span class="n"&gt;has_one&lt;/span&gt; &lt;span class="ss"&gt;:progress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dependent: :destroy&lt;/span&gt;

  &lt;span class="n"&gt;before_create&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;progress&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;Progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# models/check_in.rb&lt;/span&gt;
&lt;span class="no"&gt;CheckIn&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:engagement&lt;/span&gt;

  &lt;span class="n"&gt;delegate&lt;/span&gt; &lt;span class="ss"&gt;:completed_timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: :progress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;prefix: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;duration&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;completed_timestamp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
      &lt;span class="n"&gt;progress_completed_timestamp&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="rouge-gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
&lt;/pre&gt;&lt;/td&gt;&lt;td class="rouge-code"&gt;&lt;pre&gt;&lt;span class="c1"&gt;# models/progress.rb&lt;/span&gt;
&lt;span class="no"&gt;Class&lt;/span&gt; &lt;span class="no"&gt;Progress&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:engagement&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_in_completed_timestamp&lt;/span&gt;
    &lt;span class="n"&gt;check_in_completed_at&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In summary, there were two main reasons for a &lt;em&gt;development mystery
guest&lt;/em&gt; here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;We trusted&lt;/strong&gt; that objects supporting our code assumptions wouldn&amp;rsquo;t change on us.
And because of that, we didn&amp;rsquo;t specify the information needed from them. (The
same happens during testing when we trust fixtures or factories to have the data
for us to pass the test, instead of declaring it during setup).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;We optimized too early&lt;/strong&gt;. It is simpler to call a method in the
current object than to ask for the information to some other one. But even if
simpler, it may not be correct with future changes in the code.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;p&gt;&lt;strong&gt;Avoid assuming that the information depending on other objects will always be
as correct as it is today&lt;/strong&gt;.&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;Always specify what you need. And forget about early &amp;ldquo;optimizations&amp;rdquo;. Do them
when they make sense and are needed.  That&amp;rsquo;s how you make sure you prevent
mystery guests.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>A git branching model for 15 people sharing a single testing environment</title>
    <link rel="alternate" href="https://withatwist.dev/our-git-branching-model.html"/>
    <id>https://withatwist.dev/our-git-branching-model.html</id>
    <published>2021-04-29T00:00:00+00:00</published>
    <updated>2026-03-20T01:09:51+00:00</updated>
    <author>
      <name>Tute Costa</name>
    </author>
    <summary type="html">&lt;p&gt;My development team collaborates every day with a QA team to ship timely well-tested work. We work on a feature and put it up for testing; if QA passes it, we prepare it for production. Every week, after QAs perform full regression testing, we deploy to production (occasionally, they spot tricky bugs or regressions that we fix or revert).&lt;/p&gt;

&lt;p&gt;A typical workflow, but with a caveat: &lt;strong&gt;we share a single testing environment&lt;/strong&gt; (and thus git branch), which complicates preparing releases: either &lt;strong&gt;the shared branch contains untested code, or developers wait on QA and on each other to merge after acceptance&lt;/strong&gt;. We tried for long to solve this from scratch by provisioning more testing environments, so that developers and QA work in parallel while the shared branch only contains tested work, but in vain: we depend on a third party that so far can’t provision extra testing environments (an Electronic Health Record –EHR– company).&lt;/p&gt;

&lt;p&gt;How does this team of 12 developers and 3 QAs get their work tested on the one shared environment, deploy only acceptance-tested work weekly, without code freezes?&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;</summary>
    <content type="html">&lt;p&gt;My development team collaborates every day with a QA team to ship timely well-tested work. We work on a feature and put it up for testing; if QA passes it, we prepare it for production. Every week, after QAs perform full regression testing, we deploy to production (occasionally, they spot tricky bugs or regressions that we fix or revert).&lt;/p&gt;

&lt;p&gt;A typical workflow, but with a caveat: &lt;strong&gt;we share a single testing environment&lt;/strong&gt; (and thus git branch), which complicates preparing releases: either &lt;strong&gt;the shared branch contains untested code, or developers wait on QA and on each other to merge after acceptance&lt;/strong&gt;. We tried for long to solve this from scratch by provisioning more testing environments, so that developers and QA work in parallel while the shared branch only contains tested work, but in vain: we depend on a third party that so far can&amp;rsquo;t provision extra testing environments (an Electronic Health Record –EHR– company).&lt;/p&gt;

&lt;p&gt;How does this team of 12 developers and 3 QAs get their work tested on the one shared environment, deploy only acceptance-tested work weekly, without code freezes?&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;h2&gt;Solution zero: GitHub flow with two Epion testing environments&lt;/h2&gt;

&lt;p&gt;To ensure we&amp;rsquo;d test everything before deployments, we had two Epion testing
environments (connected to our single EHR account) where we&amp;rsquo;d test branches
before merge. Merging after tests guaranteed the shared branch only contained
accepted work and was always deployable (the simple &lt;a href="https://scottchacon.com/2011/08/31/github-flow.html"&gt;GitHub flow&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/2021-04-30-solution-zero.png" alt="git-flow tree with two Epion testing environments" /&gt;
&lt;em&gt;Commits for features 4 and 5 are on both testing environments; feature 6 awaits its turn for testing.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two environments didn&amp;rsquo;t suffice, and we went up to four, but having them all connected to the same EHR account meant changes from one could affect others, and we started seeing more unexpected errors due to the invisible shared context. Also, we grew past four parallel lines of work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We needed to go back to a single testing environment but still guarantee we wouldn’t block each other, and we&amp;rsquo;d release only tested work.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;First solution: Parallel branches&lt;/h2&gt;

&lt;p&gt;We devised a cherry-pick based git workflow with two parallel branches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;develop&lt;/code&gt; contains all commits ready for testing. Developers merge into this branch as soon as code review passes; QA tests them in our single testing environment.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;main&lt;/code&gt; contains all QA-accepted commits. Developers cherry-pick when their work is accepted, except on regression days when &lt;code&gt;main&lt;/code&gt; gets frozen, and they need to wait.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In theory, &lt;code&gt;develop&lt;/code&gt; was a superset of &lt;code&gt;main&lt;/code&gt; that contained all work ready for deployment plus some untested work. When we had a handful of cards in development that we could easily track in our minds, that was true, but &lt;strong&gt;that didn&amp;rsquo;t hold once the team grew&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For a card requiring follow-up commits, the developer may forget to cherry-pick all of them once accepted. Or a dev-only kind of task (sometimes dependency upgrades) would be invisible to QA. We&amp;rsquo;d apply it to &lt;code&gt;develop&lt;/code&gt; and forget to port it over to &lt;code&gt;main&lt;/code&gt;, erroneously believing that the &lt;code&gt;production&lt;/code&gt; version of our code had the commit. As branches diverged for long, it got harder to spot such differences.&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/2021-04-30-first-solution.png" alt="Parallel develop and main branches" /&gt;
&lt;em&gt;Features 4, 1, and 2 were accepted and cherry-picked (copied) into &lt;code&gt;main&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Other issues with this workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploying any work to production involved committing twice&lt;/li&gt;
&lt;li&gt;The team needed reminders to move accepted cards over to &lt;code&gt;main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Occasionally we&amp;rsquo;d wait for a commit that needed to go out that week, delaying regression testing and resulting in long workdays for QA&lt;/li&gt;
&lt;li&gt;Irrelevant merge conflicts arose due to different commits order on each branch&lt;/li&gt;
&lt;li&gt;The process&amp;rsquo; complexity prompted more questions, like:

&lt;ul&gt;
&lt;li&gt;Do we squash fixups in main, so it ends up being a single, revertable commit?&lt;/li&gt;
&lt;li&gt;How do I know when &lt;code&gt;main&lt;/code&gt; is frozen, or can I commit now?&lt;/li&gt;
&lt;li&gt;Do we cherry-pick from the feature branch we worked on or from &lt;code&gt;develop&lt;/code&gt;?&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As we started developing more projects in parallel, we needed to &lt;strong&gt;decouple deployments from testing&lt;/strong&gt;.
Our work is accepted straight out of the bat two-thirds of the time.
Developers should forget their work as soon as they merge, two-thirds of the times.&lt;/p&gt;

&lt;h2&gt;The new proposal: Weekly release cuts&lt;/h2&gt;

&lt;p&gt;We &lt;strong&gt;&amp;ldquo;cut a release&amp;rdquo; every Tuesday at midday, no questions asked (even with not-yet-accepted commits)&lt;/strong&gt;.
That day QA focuses on the untested cards that made it into the cut, so on regression and deploy day, all cards are Accepted (or reverted) as was guaranteed by our previous workflow.
We run deployments the following day.&lt;/p&gt;

&lt;p&gt;If a developer merges work after midday, it will miss the week’s “deploy train” and appear on the following week’s release (features 4 and 5 in the diagram).&lt;/p&gt;

&lt;p&gt;&lt;img src="/images/2021-04-30-new-proposal-1.png" alt="Release cut" /&gt;
&lt;em&gt;Release cut on feature3 by moving &lt;code&gt;main&lt;/code&gt; onto it; &lt;code&gt;develop&lt;/code&gt; continued moving forward. QA tests feature1 and 3 first.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Advantages over the first solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The happy path is &amp;ldquo;don&amp;rsquo;t make me think&amp;rdquo; simple. If a card gets accepted, it will reach production in less than a week&lt;/li&gt;
&lt;li&gt;Developers don&amp;rsquo;t need to interact with &lt;code&gt;main&lt;/code&gt; (unless preparing a deployment or they need to revert)&lt;/li&gt;
&lt;li&gt;QA can start regression testing a day before, decreasing the chance of rushed deploy days&lt;/li&gt;
&lt;li&gt;No code freezes; developers can &lt;em&gt;always&lt;/em&gt; merge their work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Given we cut the release a day (instead of hours) before regression testing starts, &lt;code&gt;develop&lt;/code&gt; usually contains Accepted commits during deploy day that won&amp;rsquo;t go out to production until the following week. The clear deadline has forced developers to try getting their priority work earlier in the sprint to maximize the chance to see production on time, alleviating the QA team&amp;rsquo;s work.
But we rarely have such urgent tasks. This process helped us see that a commit can go out next week even when ready today, which is not a problem for our teams or clients as we used to feel.&lt;/p&gt;

&lt;p&gt;When we need a task out the door after the release cut time, we
perform a smaller deployment the next day with that commit (with scoped down, faster regression testing) or postpone regression testing and deployment one day.&lt;/p&gt;

&lt;p&gt;As a disadvantage, if QA rejects a card that made it into the release, the developer must revert the commit from &lt;code&gt;main&lt;/code&gt; or prepare a (risky?) fixup. Our first solution specified reverts too, but we usually added fixup commits instead, given parallel &lt;code&gt;develop&lt;/code&gt; wasn&amp;rsquo;t under pressure. &lt;strong&gt;We began using more feature flags to turn off features dynamically, mitigating the need for &lt;code&gt;git revert&lt;/code&gt;&lt;/strong&gt;. A good practice for sure, but our team didn’t feel the need for them before and gladly incorporated them.&lt;/p&gt;

&lt;p&gt;This solution is closer to &lt;a href="https://nvie.com/posts/a-successful-git-branching-model/"&gt;git-flow&lt;/a&gt; but:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Without release branches. We maintain a single version: the one that currently runs in production, so &amp;ldquo;release&amp;rdquo; and &amp;ldquo;main&amp;rdquo; mean the same thing for us (we don&amp;rsquo;t need to backport bugfixes)&lt;/li&gt;
&lt;li&gt;Adding our definition of when develop &amp;ldquo;reaches a stable point and is ready to be released&amp;rdquo;. The first solution was &amp;ldquo;never&amp;rdquo;, and now it&amp;rsquo;s when we merge to &lt;code&gt;main&lt;/code&gt; and ask QA to prioritize untested cards in it.&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;p&gt;We&amp;rsquo;ve been running on this new workflow for a couple of months now. QA regression testing isn&amp;rsquo;t as rushed as before; we know all commits we intend for production are there; developers don&amp;rsquo;t need to wait to merge, or double-commit all their work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you find yourself in the unlucky position of needing to share fewer testing environments than lines of work, give this git workflow a try, it may help your team too.&lt;/strong&gt;&lt;/p&gt;
</content>
  </entry>
</feed>
