<?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: Alberto Arena</title>
    <description>The latest articles on DEV Community by Alberto Arena (@alberto_arena_25a48484ed5).</description>
    <link>https://dev.to/alberto_arena_25a48484ed5</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%2F1584618%2F8916c43c-3e5f-4896-b701-9e82ab63893f.jpeg</url>
      <title>DEV Community: Alberto Arena</title>
      <link>https://dev.to/alberto_arena_25a48484ed5</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alberto_arena_25a48484ed5"/>
    <language>en</language>
    <item>
      <title>My coding agent kept inventing columns</title>
      <dc:creator>Alberto Arena</dc:creator>
      <pubDate>Mon, 10 Aug 2026 14:30:52 +0000</pubDate>
      <link>https://dev.to/alberto_arena_25a48484ed5/my-coding-agent-kept-inventing-columns-1a6c</link>
      <guid>https://dev.to/alberto_arena_25a48484ed5/my-coding-agent-kept-inventing-columns-1a6c</guid>
      <description>&lt;p&gt;Ask a coding agent to write a query against a table it hasn't seen this session, and it will guess. Confidently, plausibly, and wrong: a foreign key named &lt;code&gt;author_id&lt;/code&gt; when the column is actually &lt;code&gt;created_by&lt;/code&gt;, a &lt;code&gt;status&lt;/code&gt; treated as a free-text string when it's a &lt;code&gt;tinyint&lt;/code&gt; enum, a table it's sure exists because a table like it usually does. It isn't lying, it just doesn't know, and nothing forces it to say so.&lt;/p&gt;

&lt;p&gt;The usual fix is pasting a schema dump into the chat at the start of a session. That works until the next migration, at which point it's just a different kind of wrong: confidently out of date instead of confidently invented.&lt;/p&gt;

&lt;p&gt;There's an obvious third option: let the agent run queries itself, most coding agents can already execute SQL. But that means handing over real database credentials, and a connection that can query can also see rows, not just structure. That's a bigger grant than the problem needs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/albertoarena/laravel-truss" rel="noopener noreferrer"&gt;Truss&lt;/a&gt; has always been a live, zoomable ER diagram of your Laravel app's real database schema, structure only, never a row of data. Version 1.8 points that same live structure at a coding agent instead of a browser tab.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give it meaning a type can't carry
&lt;/h2&gt;

&lt;p&gt;A column tells an agent its name and type, not what it means. &lt;code&gt;status = 1&lt;/code&gt; doesn't say "paid" on its own. Annotate it once and every export carries it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/truss.php&lt;/span&gt;
&lt;span class="s1"&gt;'annotations'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'source'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'config'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'database'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s1"&gt;'tables'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'orders'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'note'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'One row per checkout attempt, not per completed order.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'columns'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'tinyint: 0 pending, 1 paid, 2 refunded'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your database already carries &lt;code&gt;COMMENT&lt;/code&gt; strings on tables and columns, leave &lt;code&gt;'database'&lt;/code&gt; in &lt;code&gt;annotations.source&lt;/code&gt; and Truss reads those directly, no duplicate config to keep in sync. Either way, a comment is part of the &lt;code&gt;CREATE TABLE&lt;/code&gt; definition, not a row: still structure only. Strip them from any single export with &lt;code&gt;--no-annotations&lt;/code&gt; when you just want the bare shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trim it to what the question needs
&lt;/h2&gt;

&lt;p&gt;A forty-table schema is a lot of tokens to spend on a question about one table. &lt;code&gt;--compact&lt;/code&gt; drops column defaults and non-unique indexes without dropping a single table, column, or foreign key. &lt;code&gt;--focus=orders --depth=1&lt;/code&gt; narrows the export to one table and its foreign-key neighbourhood, the same idea as the dashboard's focus mode, now available from the command line. And there's a new &lt;code&gt;llm&lt;/code&gt; format alongside the existing five (DBML, JSON, CSV, Markdown, Mermaid), a dense plaintext export tuned for a token budget rather than for a human reading a data dictionary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan truss:export &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;llm &lt;span class="nt"&gt;--focus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;orders &lt;span class="nt"&gt;--depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nt"&gt;--compact&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's what the export produces. Calling it is just as direct, in code or over HTTP.&lt;/p&gt;

&lt;p&gt;Building the same thing in code goes through a new fluent, immutable &lt;code&gt;Truss&lt;/code&gt; facade instead of the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Truss&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'orders'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;compact&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toDbml&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a gated &lt;code&gt;GET {prefix}/export/{format}&lt;/code&gt; route serves the identical output to any HTTP client, behind the same &lt;code&gt;viewTruss&lt;/code&gt; gate as the dashboard. Command, facade, route, dashboard download: one pipeline underneath all four, so they can never quietly disagree with each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ask it live, instead of pasting a snapshot
&lt;/h2&gt;

&lt;p&gt;The part I actually wanted, though, wasn't a better export. It was not exporting anything at all.&lt;/p&gt;

&lt;p&gt;Truss 1.8 adds an optional server for MCP, the Model Context Protocol that Claude Code, Claude Desktop, and Cursor use to reach outside tools. Built on &lt;code&gt;laravel/mcp&lt;/code&gt;, it talks to a coding agent directly over local stdio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require laravel/mcp
php artisan mcp:start truss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point one at it and the agent gets five tools, &lt;code&gt;list_tables&lt;/code&gt;, &lt;code&gt;describe_table&lt;/code&gt;, &lt;code&gt;get_schema&lt;/code&gt;, &lt;code&gt;focus_table&lt;/code&gt;, and &lt;code&gt;get_structural_review&lt;/code&gt;, plus a &lt;code&gt;truss://schema&lt;/code&gt; resource, all reading the live schema on demand. Every tool advertises MCP's &lt;code&gt;readOnlyHint&lt;/code&gt;, so a client can present them as read-only instead of prompting for write approval on a call that was never going to write anything. No row data, ever, and the same exclusion and managed-connection safeguards as the rest of Truss apply here too, opt-in and off by default behind &lt;code&gt;truss.mcp.enabled&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I pointed it at a real project I've worked on for a while, in Claude Desktop, and the difference was immediate: instead of me pasting a schema dump at the start of the conversation, or the agent asking me to run a query to check a column name, it just called &lt;code&gt;describe_table&lt;/code&gt; before it wrote anything, the same check that would have caught the guessed &lt;code&gt;author_id&lt;/code&gt; from the start of this post. No staleness, because there's nothing to go stale, it's reading the same live introspection the diagram uses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://trussphp.com/demo/" rel="noopener noreferrer"&gt;Live demo&lt;/a&gt;, running against a fictional schema&lt;/li&gt;
&lt;li&gt;AI context guide: &lt;a href="https://trussphp.com/guides/ai-context/" rel="noopener noreferrer"&gt;trussphp.com/guides/ai-context&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MCP server guide: &lt;a href="https://trussphp.com/guides/mcp-server/" rel="noopener noreferrer"&gt;trussphp.com/guides/mcp-server&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Full changelog: &lt;a href="https://github.com/albertoarena/laravel-truss/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;CHANGELOG.md on GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Update with &lt;code&gt;composer update albertoarena/laravel-truss&lt;/code&gt;, and if you want the MCP server too, &lt;code&gt;composer require laravel/mcp&lt;/code&gt; on top.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;More &lt;code&gt;truss:doctor&lt;/code&gt; rules and CI-native output formats are next on the &lt;a href="https://trussphp.com/roadmap/" rel="noopener noreferrer"&gt;roadmap&lt;/a&gt;, followed by reading Eloquent relationships for semantic edge labels instead of raw foreign keys, and navigation aids for schemas with a hundred tables or more. If a tool the agent needs isn't there yet, or an annotation source you'd want isn't supported, &lt;a href="https://github.com/albertoarena/laravel-truss/discussions" rel="noopener noreferrer"&gt;open a discussion&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>ai</category>
      <category>database</category>
    </item>
    <item>
      <title>Claude Code Auto Mode: What Still Needs a Human</title>
      <dc:creator>Alberto Arena</dc:creator>
      <pubDate>Mon, 10 Aug 2026 08:43:01 +0000</pubDate>
      <link>https://dev.to/alberto_arena_25a48484ed5/claude-code-auto-mode-what-still-needs-a-human-kg5</link>
      <guid>https://dev.to/alberto_arena_25a48484ed5/claude-code-auto-mode-what-still-needs-a-human-kg5</guid>
      <description>&lt;p&gt;Starting August 14, &lt;a href="https://claude.com/blog/auto-mode-default-in-claude-code" rel="noopener noreferrer"&gt;auto mode becomes the default permission mode&lt;/a&gt; for Claude Code on Pro, Max, and Team plans. Buried in Anthropic's own announcement is a number worth sitting with: in a controlled test with 1,053 paid professional testers, researchers swapped in genuinely dangerous commands partway through real sessions. Humans caught 13.6% of them. Auto mode's classifier caught 89%.&lt;/p&gt;

&lt;p&gt;That's not a subtle gap, and reading it made me a little uncomfortable, because I've spent most of the last few months running my own sessions in accept-edits or auto mode, clicking through permission prompts the same way everyone apparently does. Anthropic's own usage data backs that up: people approve 97% of ordinary permission prompts, and the rate at which someone actually catches a dangerous one degrades from roughly 17% early in a session to about 5% after fifty-plus prior approvals. We're not reading carefully. We're pattern-matching on "this looks like the last forty things I clicked yes on," and that pattern breaks exactly when it stops holding.&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;Human review&lt;/th&gt;
&lt;th&gt;Auto mode classifier&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dangerous commands caught (1,053-tester study)&lt;/td&gt;
&lt;td&gt;13.6%&lt;/td&gt;
&lt;td&gt;89%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Production-severity unintended harm, real flagged sessions&lt;/td&gt;
&lt;td&gt;6.3%&lt;/td&gt;
&lt;td&gt;2.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The second row comes from Anthropic's audit of real, already-flagged sessions rather than a lab test: manually-approved sessions contained serious unintended harm more than twice as often as auto mode sessions did. Different methodology, same direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually changes on August 14
&lt;/h2&gt;

&lt;p&gt;Auto mode itself is a fairly simple change to the default flow. Right now, Claude pauses before most file edits and shell commands and waits for you to click approve. Under auto mode, it doesn't. Instead, a separate classifier model reviews each action in the background as Claude proposes it, and only interrupts you when it judges something risky: an irreversible action, a request that escalates beyond what you asked for, an attempt to send data somewhere it shouldn't go. Everything else, the reads, the routine edits, the ordinary shell commands, runs without a prompt at all.&lt;/p&gt;

&lt;p&gt;The classifier sits underneath the permission mode you've already picked, not instead of it. Your allow, ask, and deny rules resolve first; the classifier only gets a say on whatever those rules don't already settle. That layering is exactly what makes the rest of this post possible: the classifier isn't the only thing standing between Claude and a bad command, it's the last one, and everything upstream of it is something you configure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The asterisk Anthropic puts on its own number
&lt;/h2&gt;

&lt;p&gt;None of this means auto mode is safe, and Anthropic doesn't claim it is. Their own line: "auto mode relies on classification systems and therefore does not eliminate risk." They ran a two-week red-teaming pilot with &lt;a href="https://watcher.apolloresearch.ai/" rel="noopener noreferrer"&gt;Apollo Research&lt;/a&gt;, injecting synthetic attacks into real workflows. Before the hardening that shipped with this release, the classifier missed 12% of those attacks. After hardening, 7%, on a test set the classifier had never seen during tuning. Better than a distracted human. Not zero.&lt;/p&gt;

&lt;p&gt;What the classifier is actually built to catch is specific: scope escalation on a vague or destructive request, credential exploration, exfiltration through an external service like a public gist, retrying a failed deploy with a skip-verification flag, a command that reads normally but was seeded by hostile content Claude picked up along the way. Where it still misses isn't usually "is this dangerous", it correctly flags danger most of the time. It's "does a real consent signal in this session actually cover this specific action", the same ambiguity a tired human glosses over, just less often.&lt;/p&gt;

&lt;p&gt;So the useful question isn't auto mode versus manual review. It's what to put alongside auto mode that fails in a different way than auto mode does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rules that hold no matter the mode
&lt;/h2&gt;

&lt;p&gt;The classifier isn't the only layer available, and it's not the one you have the most control over. Permission rules sit underneath it: &lt;a href="https://code.claude.com/docs/en/permission-modes" rel="noopener noreferrer"&gt;the docs are explicit&lt;/a&gt; that "these controls apply in every mode, including &lt;code&gt;bypassPermissions&lt;/code&gt;: deny rules and explicit ask rules." Deny is a hard stop the classifier never gets to weigh in on. Ask still forces a prompt, even in a mode whose entire point is skipping prompts, which matters because the example below relies on it.&lt;/p&gt;

&lt;p&gt;Here's a concrete gap worth knowing about: auto mode auto-approves pushes to any branch of the repository you're working in, including the default branch, unless you tell it otherwise. The classifier judges the push's content on its own terms, but the act of pushing to &lt;code&gt;main&lt;/code&gt; isn't itself something it blocks by default. If you want that to always stop for a human, write it down:&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;"permissions"&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;"ask"&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="s2"&gt;"Bash(git push origin main)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(git push origin master)"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The push example above uses &lt;code&gt;ask&lt;/code&gt; on purpose: you still want to push eventually, just with a human in the loop first. That's different from an invariant you never want crossed at all, "no data exposed, ever," say, in a &lt;code&gt;CLAUDE.md&lt;/code&gt; file. A rules file states that kind of invariant as a request Claude tries to honor. A deny rule enforces it: a request the tool can't route around, in any mode, on any day you forget you're in auto mode at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  A gate automation can't talk itself out of
&lt;/h2&gt;

&lt;p&gt;Rules cover what you can name in advance. Hooks cover what you want checked every time, mechanically, regardless of what the classifier decides. A &lt;code&gt;PreToolUse&lt;/code&gt; hook can inspect a tool call before it runs and block it outright, deterministically, with no probability involved:&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;"hooks"&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;"PreToolUse"&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;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"if"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bash(git push *)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${CLAUDE_PROJECT_DIR}/.claude/hooks/require-green-tests.sh"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# .claude/hooks/require-green-tests.sh&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; npm &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--silent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Tests are red, push blocked"}}'&lt;/span&gt;
&lt;span class="k"&gt;else
  &lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;if&lt;/code&gt; field narrows this to &lt;code&gt;git push&lt;/code&gt; calls specifically, the script runs the test suite, and a failing suite returns a &lt;code&gt;deny&lt;/code&gt; decision that blocks the push outright, exit code 2 works the same way with a plain stderr message. Nothing here asks a classifier to judge intent. It's a yes/no gate on a fact you can check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review moves, it doesn't disappear
&lt;/h2&gt;

&lt;p&gt;None of the above replaces reading the diff. It changes what "reading the diff" is for. &lt;a href="https://dev.to/posts/we-became-editors-in-chief/"&gt;I wrote before about how agentic development turned most of the job into review&lt;/a&gt;, and the failure mode there isn't that people stopped reviewing, it's that review quality degrades under volume exactly the way the approval-fatigue numbers above describe. Auto mode doesn't fix that. It just removes the low-value prompts so the review that's left can, in theory, be the kind that actually catches something.&lt;/p&gt;

&lt;p&gt;In practice that means picking one point in the loop where you deliberately stay the bottleneck, on purpose, and the push to a shared branch is the obvious candidate. It's the one action that's genuinely hard to undo and visible to someone else the moment it happens. Everything upstream of that, a wrong edit, a bad approach, a half-finished refactor, is still local and still reversible. &lt;a href="https://dev.to/posts/ai-hallucination-in-coding-agents/"&gt;Hallucinated code compiles and passes a quick glance&lt;/a&gt;; it doesn't survive someone actually reading it against what they asked for. That reading has to happen somewhere. Put it at the boundary that matters and let the classifier and the hooks handle the volume everywhere else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tests are the check that doesn't get tired
&lt;/h2&gt;

&lt;p&gt;The hook above only works because there's a test suite behind it. That's the other half of the strategy: tests are the executable version of "did this actually do what I asked", and unlike a human on prompt four hundred of a session, they don't get less careful over time. Write them before or alongside the change, not after, and the &lt;code&gt;git push&lt;/code&gt; gate above stops being a formality and starts being the thing that actually catches a regression a tired reviewer would have waved through.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stacking checks that fail differently
&lt;/h2&gt;

&lt;p&gt;None of this makes the miss rate zero, same as auto mode itself doesn't. The point isn't finding the one control that eliminates risk. It's stacking controls that fail in different ways, so the same blind spot doesn't line up twice. The classifier catches the obvious and the fast. Deny rules catch the specific thing you already know matters. Hooks catch what you can specify as a check. Tests catch what you can assert. And a deliberate human read at the point something becomes shared catches the one thing none of the above can judge: whether this was the right thing to build in the first place.&lt;/p&gt;

&lt;p&gt;Thirteen point six percent is a bad number for humans working alone. Eighty-nine percent is a good number for a classifier working alone. Neither one is the number that matters. The number that matters is what's left over once you've stacked both, plus the checks that are actually yours to configure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://claude.com/blog/auto-mode-default-in-claude-code" rel="noopener noreferrer"&gt;Auto mode is now the default in Claude Code for Pro, Max, and Team plans&lt;/a&gt;: the announcement this post responds to, including the 1,053-tester study and the real-session audit numbers.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.anthropic.com/engineering/claude-code-auto-mode" rel="noopener noreferrer"&gt;How we built Claude Code auto mode: a safer way to skip permissions&lt;/a&gt;: the technical writeup covering the classifier's two-stage design and what it's built to catch.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://code.claude.com/docs/en/permission-modes" rel="noopener noreferrer"&gt;Choose a permission mode&lt;/a&gt;: official documentation on &lt;code&gt;default&lt;/code&gt;, &lt;code&gt;acceptEdits&lt;/code&gt;, &lt;code&gt;bypassPermissions&lt;/code&gt;, and how permission rules interact with each mode.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://code.claude.com/docs/en/hooks" rel="noopener noreferrer"&gt;Hooks reference&lt;/a&gt;: official documentation on hook events, matcher syntax, and the &lt;code&gt;PreToolUse&lt;/code&gt; block/allow contract used in the example above.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>programming</category>
      <category>devops</category>
    </item>
    <item>
      <title>I built a Laravel event-sourcing generator, then the AI version</title>
      <dc:creator>Alberto Arena</dc:creator>
      <pubDate>Thu, 06 Aug 2026 06:01:00 +0000</pubDate>
      <link>https://dev.to/alberto_arena_25a48484ed5/i-built-a-laravel-event-sourcing-generator-then-the-ai-version-422i</link>
      <guid>https://dev.to/alberto_arena_25a48484ed5/i-built-a-laravel-event-sourcing-generator-then-the-ai-version-422i</guid>
      <description>&lt;p&gt;A while back I built a &lt;a href="https://dev.to/posts/laravel-event-sourcing-generator-10k/"&gt;code generator for event sourcing in Laravel&lt;/a&gt;: run one artisan command against &lt;code&gt;spatie/laravel-event-sourcing&lt;/code&gt;, get aggregates, events, projectors, and reactors scaffolded out. It's deterministic. Same inputs, same output, every time. It just crossed 10,000 downloads on Packagist.&lt;/p&gt;

&lt;p&gt;More recently I built a &lt;a href="https://dev.to/posts/ai-laravel-event-sourcing/"&gt;Claude Code skill&lt;/a&gt; that produces the same kind of domain code, except it starts with a conversation instead of a command. So the question people keep sending me is obvious: does the AI skill replace the generator?&lt;/p&gt;

&lt;p&gt;No. And the reason is more interesting than the question.&lt;/p&gt;

&lt;p&gt;The short version, if you skip everything else: AI didn't kill the code generator. It took over the &lt;em&gt;design&lt;/em&gt; half of the job and left the deterministic half exactly where determinism still matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the generator is good at
&lt;/h2&gt;

&lt;p&gt;The generator's whole value proposition is determinism. &lt;code&gt;composer require albertoarena/laravel-event-sourcing-generator&lt;/code&gt;, then &lt;code&gt;php artisan make:event-sourcing-domain Order&lt;/code&gt;, and you get exactly the files you'd expect, byte-identical every run. No tokens, no API key, no network dependency, nothing to review for "did it hallucinate a method." It runs the same in CI as it does on my laptop.&lt;/p&gt;

&lt;p&gt;That's exactly right when I already know the shape of the domain: I know the aggregate boundary, I know the events, I just need the boilerplate written consistently. What it can't do is help me figure out whether that boundary is correct in the first place. It assumes I already did the hard part.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the skill is good at
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/albertoarena/claude-laravel-event-sourcing" rel="noopener noreferrer"&gt;skill&lt;/a&gt; is not a code generator wearing an AI costume. Its value sits upstream of the code, in a two-gate workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gate 1, design.&lt;/strong&gt; It asks focused questions about the domain and produces an Architecture Decision Record: aggregates, commands, events, projectors, reactors, and the invariants holding them together. Nothing gets written until I approve that ADR.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gate 2, implementation.&lt;/strong&gt; Once I approve it, it writes tests first, then generates the full domain, and runs the suite. For that &lt;code&gt;cancel&lt;/code&gt;-after-&lt;code&gt;ship&lt;/code&gt; rule, that means a test asserting the aggregate throws before a line of the aggregate itself exists, not after.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The part that actually matters is Gate 1. "Can &lt;code&gt;cancel&lt;/code&gt; still fire after &lt;code&gt;ship&lt;/code&gt;?" is a modelling question, and that's the conversation where event sourcing lives or dies. The generator was never going to have that conversation with me. It doesn't ask questions; it executes a decision I've already made.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real dividing line
&lt;/h2&gt;

&lt;p&gt;Both tools end up emitting similar-looking PHP, which is why people fixate on the overlap. But codegen is the least interesting thing either one does. The line that actually matters is this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;I already know the design → the generator.&lt;/strong&gt; Deterministic, fast, no review tax, no dependency on an LLM being available or correct.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I'm still figuring out the design → the skill.&lt;/strong&gt; It's a modelling partner that leaves a paper trail (the ADR) and a safety net (tests written before the implementation exists).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Which one I actually reach for
&lt;/h2&gt;

&lt;p&gt;Honestly, mostly the skill these days. Most of the time I sit down to add a new bounded context, the boundary isn't fully settled yet, and paying for a five-minute design conversation before anything gets written is worth more than skipping straight to boilerplate. The ADR forces me to answer the awkward "wait, should that be an event or a side effect?" questions before they turn into a rewrite.&lt;/p&gt;

&lt;p&gt;That doesn't mean the generator's properties stopped mattering. It still gets pulled out whenever the reasons I built it in the first place are the reasons that matter: expanding a domain whose shape is already locked in, running in a pipeline where I don't want an LLM in the loop, or anywhere determinism and zero token cost outrank a design conversation I don't need to have again. I just don't hit that situation as often as I used to, now that the skill exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I don't use the skill at all
&lt;/h2&gt;

&lt;p&gt;Being upfront about the edges is the point of writing this. The skill is greenfield only. It designs new event-sourced domains; it does not refactor existing CRUD into event sourcing, and it's scoped specifically to &lt;code&gt;spatie/laravel-event-sourcing&lt;/code&gt; v7, not Laravel's own event system, not CQRS without event sourcing, not other packages. If that's your situation, neither tool here is the answer, and I'd rather say so than sell you a mismatch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try either
&lt;/h2&gt;

&lt;p&gt;If you're modelling a new event-sourced domain and Claude Code is already part of your workflow, the skill installs in two steps: the first points Claude Code at the marketplace (nothing installs yet), the second installs the plugin from it.&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 albertoarena/claude-laravel-event-sourcing
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;laravel-spatie-event-sourcing@albertoarena
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you already know the shape of the domain and just want the boilerplate, the generator is a Composer install away:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require albertoarena/laravel-event-sourcing-generator
php artisan make:event-sourcing-domain Order
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full write-ups of each, if you want the mechanics: &lt;a href="https://dev.to/posts/laravel-event-sourcing-generator-10k/"&gt;the generator&lt;/a&gt;, &lt;a href="https://dev.to/posts/ai-laravel-event-sourcing/"&gt;the skill&lt;/a&gt;, and the &lt;a href="https://dev.to/posts/domain-using-spatie-event-sourcing/"&gt;original walkthrough of building a domain with Spatie event sourcing&lt;/a&gt; if you're new to any of this.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>laravel</category>
      <category>php</category>
      <category>code</category>
    </item>
    <item>
      <title>I gave my schema viewer your app's colours</title>
      <dc:creator>Alberto Arena</dc:creator>
      <pubDate>Tue, 04 Aug 2026 11:25:25 +0000</pubDate>
      <link>https://dev.to/alberto_arena_25a48484ed5/i-gave-my-schema-viewer-your-apps-colours-1nhd</link>
      <guid>https://dev.to/alberto_arena_25a48484ed5/i-gave-my-schema-viewer-your-apps-colours-1nhd</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/albertoarena/laravel-truss" rel="noopener noreferrer"&gt;Truss&lt;/a&gt; is a live, zoomable ER diagram of your Laravel app's actual database schema, safe to run in production because it only ever reads structure. I introduced it &lt;a href="https://dev.to/posts/introducing-truss/"&gt;back in July&lt;/a&gt;, and it's been part of every client project since, gated behind a &lt;code&gt;viewTruss&lt;/code&gt; policy. Two things kept nagging at me. The dashboard was always Truss blue, whatever the client's actual brand was, so it looked bolted onto their app rather than part of it. And the export button, one click for a Markdown data dictionary or a DBML file, only ever got clicked when I remembered to, so the schema file committed to the repo drifted quietly from the real one until someone noticed, usually the hard way.&lt;/p&gt;

&lt;p&gt;Truss 1.6 fixes both: a themeable dashboard that matches whatever it's embedded in, and a &lt;code&gt;truss:export&lt;/code&gt; command that puts the same output in your terminal and your CI pipeline, no browser required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Match it to your app
&lt;/h2&gt;

&lt;p&gt;Colours and fonts now live under &lt;code&gt;truss.theme&lt;/code&gt; in config, and Truss re-skins itself in both light and dark mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/truss.php&lt;/span&gt;
&lt;span class="s1"&gt;'theme'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'colors'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'light'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'accent'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'#b45309'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'accent-secondary'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'#0f766e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'background'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'#faf6f0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'surface'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'#fffdf8'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'text'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'#3a2a1c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'border'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'#c8873f'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;'dark'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'accent'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'#fbbf24'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'background'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'#1b130b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'surface'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'#26190d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'text'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'#f5e6d0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'border'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'#8a6428'&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;Only the knobs you set are overridden, everything else stays on the default Blueprint palette, so three or four values are enough to make it look at home. The re-skin runs deep: the diagram's relationship lines, label backdrops, background grid, table rows, and inputs all follow, not just the toolbar chrome. It ships as a same-origin stylesheet, no build step, &lt;code&gt;style-src 'self'&lt;/code&gt; still covers it, and a default install adds no extra request at all. Each value is validated before it's written, so a typo falls back to the default instead of breaking the sheet.&lt;/p&gt;

&lt;p&gt;Picking hex values by hand isn't required. The new &lt;a href="https://trussphp.com/theme-builder/" rel="noopener noreferrer"&gt;theme builder&lt;/a&gt; previews a palette against the real dashboard live, starts from presets (Blueprint, Ember, Contrast) if you'd rather tweak than start blank, and generates the exact &lt;code&gt;config/truss.php&lt;/code&gt; snippet to paste in.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffaz9xz7rdz8detx0ay4b.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffaz9xz7rdz8detx0ay4b.webp" alt="The Truss theme builder in light mode: colour knobs on the left, the schema diagram re-skinned in a warm amber and teal palette on the right" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Get a schema file without opening a browser
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan truss:export &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dbml &lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;docs/schema.dbml
php artisan truss:export &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;json &lt;span class="nt"&gt;--check&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;truss:export&lt;/code&gt; is the export button's command-line counterpart: the same five formats (DBML, JSON, CSV, Markdown data dictionary, Mermaid), generated straight from PHP, no browser involved. Output is deterministic, the same schema always produces the same bytes, which is what makes &lt;code&gt;--check&lt;/code&gt; worth running: point it at the file you've committed, and it exits &lt;code&gt;1&lt;/code&gt; the moment that file stops matching the database, &lt;code&gt;2&lt;/code&gt; on a usage error, &lt;code&gt;0&lt;/code&gt; when everything lines up. Wire it into a commit hook or CI, and schema drift stops being something a reviewer has to notice by eye. Structure only, as always, no network call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Theme builder: &lt;a href="https://trussphp.com/theme-builder/" rel="noopener noreferrer"&gt;trussphp.com/theme-builder&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Live demo: &lt;a href="https://trussphp.com/demo/" rel="noopener noreferrer"&gt;trussphp.com/demo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Theming guide: &lt;a href="https://trussphp.com/guides/theming/" rel="noopener noreferrer"&gt;trussphp.com/guides/theming&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Schema export guide: &lt;a href="https://trussphp.com/guides/schema-export/" rel="noopener noreferrer"&gt;trussphp.com/guides/schema-export&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Release notes: &lt;a href="https://github.com/albertoarena/laravel-truss/releases/tag/v1.6.0" rel="noopener noreferrer"&gt;v1.6.0 on GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Update with &lt;code&gt;composer update albertoarena/laravel-truss&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Lighthouse CI is next on the &lt;a href="https://trussphp.com/roadmap/" rel="noopener noreferrer"&gt;roadmap&lt;/a&gt;: automated performance and accessibility audits across both themes, light and dark. After that: more &lt;code&gt;truss:doctor&lt;/code&gt; rules, reading Eloquent relationships for semantic labels instead of just foreign keys, and schema context formatted for AI agents working in the same codebase. If a theme knob is missing or &lt;code&gt;truss:export&lt;/code&gt; should support a format it doesn't, &lt;a href="https://github.com/albertoarena/laravel-truss/discussions" rel="noopener noreferrer"&gt;open a discussion&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>database</category>
      <category>opensource</category>
    </item>
    <item>
      <title>We Became Editors-in-Chief, and Nobody Trained Us</title>
      <dc:creator>Alberto Arena</dc:creator>
      <pubDate>Mon, 03 Aug 2026 06:13:59 +0000</pubDate>
      <link>https://dev.to/alberto_arena_25a48484ed5/we-became-editors-in-chief-and-nobody-trained-us-2j49</link>
      <guid>https://dev.to/alberto_arena_25a48484ed5/we-became-editors-in-chief-and-nobody-trained-us-2j49</guid>
      <description>&lt;p&gt;Most of my working day now looks like this: I write a prompt describing what I want. An agent produces a diff. I read it, and either approve it or ask for a refinement. Repeat, dozens of times, until the feature is done.&lt;/p&gt;

&lt;p&gt;Ask whether that makes us "prompt engineers" and you're asking the wrong question. Prompt wording is a thin skill, learnable in an afternoon. What's actually changed, after twenty-seven years of writing code the old way, is what the job does to your attention. Less typing. Much more reviewing, at a volume and pace nothing in my career prepared me for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop, honestly described
&lt;/h2&gt;

&lt;p&gt;Strip away the branding and agentic development is a review cycle. You describe an outcome. The agent proposes an implementation. You check it against a mental model of what "correct" means for this codebase, this edge case, this architecture, and you either accept it or push back with more context.&lt;/p&gt;

&lt;p&gt;That's not a new activity. It's code review, except the author on the other side never gets tired, never pushes back, and produces a plausible-looking diff in seconds whether it's right or not. The volume changes everything. When review is the bottleneck instead of typing, the job's centre of gravity moves with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A different kind of tired
&lt;/h2&gt;

&lt;p&gt;The old way was stressful in bursts. You'd hit a bug, lose an afternoon to it, feel the specific frustration of a stack trace that refuses to make sense. Stressful, but the pace was yours. Typing is slow. Debugging forces pauses. Whether you wanted it or not, there was slack built into the rhythm.&lt;/p&gt;

&lt;p&gt;Agentic coding removes that slack. The agent doesn't pace itself to your attention span. It just keeps producing diffs, one after another, faster than you'd ever type them yourself. To review well you need prolonged, focused attention, held at a level that used to only show up during the hard parts. There's no natural lull left for your brain to reset in, because generation never slows down to let you catch up. A whole afternoon of that is a different kind of tired than a whole afternoon spent stuck on one stubborn bug, and after twenty-seven years of the old rhythm, this one still catches me off guard some days.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Prompt engineer" is the wrong name for it
&lt;/h2&gt;

&lt;p&gt;The skill that matters here was never prompt wording. It's knowing what correct looks like before you see the diff: why a query needs an index, why a migration is unsafe under concurrent writes, why a "working" abstraction will hurt in six months. That judgment doesn't come from prompting practice. It comes from having built things, broken things, and paid for the mistakes.&lt;/p&gt;

&lt;p&gt;The people getting the most out of agentic tools right now aren't the ones with the cleverest prompts. They're the ones who can look at generated code and immediately spot the one line that's subtly wrong, because they've written that exact bug themselves at some point. Prompting is the interface. Engineering judgment is still the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this goes in five years
&lt;/h2&gt;

&lt;p&gt;Three things seem likely to me, in no particular order of confidence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Review becomes the skill we train for, not the one we tolerate.&lt;/strong&gt; Right now most of us learned to review code as a secondary skill, something you did after learning to write it. If specifying and judging becomes the primary loop, reviewing well, fast and without rubber-stamping, has to become something we deliberately get better at, the way we once deliberately got better at algorithms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We start designing around reviewer fatigue, not just reviewer speed.&lt;/strong&gt; Right now most tooling optimises for how fast an agent can produce a diff. The actual constraint is how long a human can sustain high-attention review before quality drops. I'd expect smaller, more frequent checkpoints, better summaries of what changed and why before you dive into the diff itself, and more deliberate pacing, because burning out the reviewer is now the real bottleneck, not the code generation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The gap between good and bad engineers widens, not narrows.&lt;/strong&gt; Agentic tools are a multiplier, and multipliers amplify what's already there. An engineer with strong judgment reviews faster and catches more. An engineer without it approves more bugs, faster than before. The floor for "can produce working code" drops. The ceiling for "can be trusted to ship it" doesn't move, or gets more valuable precisely because it's rarer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I'm less sure about
&lt;/h2&gt;

&lt;p&gt;What worries me isn't the loop itself: it's whether the judgment that makes the loop work survives if fewer people spend years building it the hard way. Review is only as good as the model you're reviewing against, and that model used to be built by writing thousands of lines yourself, badly, and fixing them. If the next generation of engineers reviews before it ever really writes, I don't know yet whether that produces the same instincts faster, or a generation of reviewers who can't tell a plausible diff from a correct one.&lt;/p&gt;

&lt;p&gt;So no, I don't think we're becoming prompt engineers. I think we're becoming editors-in-chief of our own code, and editing well is its own craft, one most of us are still learning on the job. Where that leaves the next five years is the part I'm genuinely curious about, not the part I have a tidy answer for.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://albertoarena.it/posts/we-became-editors-in-chief/" rel="noopener noreferrer"&gt;albertoarena.it&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>career</category>
      <category>productivity</category>
      <category>discuss</category>
    </item>
    <item>
      <title>The schema doctor is in</title>
      <dc:creator>Alberto Arena</dc:creator>
      <pubDate>Fri, 31 Jul 2026 05:05:07 +0000</pubDate>
      <link>https://dev.to/alberto_arena_25a48484ed5/the-schema-doctor-is-in-i8k</link>
      <guid>https://dev.to/alberto_arena_25a48484ed5/the-schema-doctor-is-in-i8k</guid>
      <description>&lt;p&gt;A migration that adds a &lt;code&gt;user_id&lt;/code&gt; column with no supporting index passes review every time. It looks fine. Laravel doesn't complain, the migration runs clean, CI is green. Then a few months later someone's staring at a JOIN that touches two hundred rows and takes eight seconds, and nobody remembers why.&lt;/p&gt;

&lt;p&gt;That's the class of problem &lt;a href="https://github.com/albertoarena/laravel-truss" rel="noopener noreferrer"&gt;Truss&lt;/a&gt; 1.5 is built to catch before it ships: the schema doctor.&lt;/p&gt;

&lt;p&gt;I wrote about &lt;a href="https://dev.to/posts/introducing-truss/"&gt;Truss&lt;/a&gt; here in July, a live, zoomable ER diagram of your Laravel app's actual database schema. It's picked up more traction than I expected since then: it's closing in on 300 installs on Packagist and past 70 stars on GitHub, almost entirely word of mouth in the Laravel community. That traction is exactly what pushed the next feature: a diagram is great for seeing your schema, but it doesn't tell you when something in it is wrong. &lt;code&gt;truss:doctor&lt;/code&gt; does.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it catches
&lt;/h2&gt;

&lt;p&gt;Run it and it flags problems that are visible from structure alone:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tables with no primary key&lt;/li&gt;
&lt;li&gt;Foreign keys with no supporting index&lt;/li&gt;
&lt;li&gt;Duplicate or redundant indexes&lt;/li&gt;
&lt;li&gt;Foreign keys whose type doesn't match the key they reference&lt;/li&gt;
&lt;li&gt;Money-looking columns stored as &lt;code&gt;float&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Pivots without a unique key, unindexed &lt;code&gt;deleted_at&lt;/code&gt;, and more&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thirteen rules in total, across integrity, index, and type categories, each with a stable code (like &lt;code&gt;TRUSS-IDX-001&lt;/code&gt;) so you can look it up, silence it, or change its severity.&lt;/p&gt;

&lt;p&gt;Rules are engine-aware where it matters: an unindexed foreign key is an &lt;strong&gt;error&lt;/strong&gt; on PostgreSQL and SQLite, but only &lt;strong&gt;info&lt;/strong&gt; on MySQL and MariaDB, since those auto-index foreign keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  In the terminal and CI
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan truss:doctor
php artisan truss:doctor &lt;span class="nt"&gt;--preset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;strict &lt;span class="nt"&gt;--fail-on&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;warning
php artisan truss:doctor &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It exits non-zero once a finding hits your fail level, so a migration that introduces a problem fails the build. No AI, no queries, no row data: deterministic and structure-only, safe to run in a commit hook or a CI pipeline against a database you don't fully trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  In the dashboard
&lt;/h2&gt;

&lt;p&gt;The same findings surface in the dashboard as a new Health panel, the heart icon in the toolbar. Tables with a problem are flagged right on the diagram, the offending column is marked inline, and lower-confidence (heuristic) findings are labelled as such. Same engine, same findings, two front ends depending on whether you're at a terminal or in the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://trussphp.com/demo/" rel="noopener noreferrer"&gt;live demo&lt;/a&gt; has it running against a fictional schema. Open the Health panel and you'll see a handful of foreign keys flagged for a missing index.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Live demo: &lt;a href="https://trussphp.com/demo/" rel="noopener noreferrer"&gt;trussphp.com/demo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Guide, with every rule code: &lt;a href="https://trussphp.com/guides/schema-doctor/" rel="noopener noreferrer"&gt;trussphp.com/guides/schema-doctor&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Release notes: &lt;a href="https://github.com/albertoarena/laravel-truss/releases/tag/v1.5.0" rel="noopener noreferrer"&gt;v1.5.0 on GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Update with &lt;code&gt;composer update albertoarena/laravel-truss&lt;/code&gt;, then run &lt;code&gt;php artisan truss:doctor&lt;/code&gt; and see what's already in there.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;This is phase one: the engine and the thirteen rules. Suppression files, more rules, and CI formatters are on the &lt;a href="https://trussphp.com/roadmap/" rel="noopener noreferrer"&gt;roadmap&lt;/a&gt;. If a rule feels too noisy, too quiet, or there's a check you'd want that isn't there yet, the &lt;a href="https://github.com/albertoarena/laravel-truss/discussions/18" rel="noopener noreferrer"&gt;discussion thread&lt;/a&gt; is open. I'd love to hear what you run it against.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>database</category>
      <category>opensource</category>
    </item>
    <item>
      <title>CLAUDE.md Is RAM, Skills Are Not Disk: The Four-Tier Memory Model for Claude Code</title>
      <dc:creator>Alberto Arena</dc:creator>
      <pubDate>Mon, 27 Jul 2026 10:30:21 +0000</pubDate>
      <link>https://dev.to/alberto_arena_25a48484ed5/claudemd-is-ram-skills-are-not-disk-the-four-tier-memory-model-for-claude-code-1cp8</link>
      <guid>https://dev.to/alberto_arena_25a48484ed5/claudemd-is-ram-skills-are-not-disk-the-four-tier-memory-model-for-claude-code-1cp8</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdbpoxacgegg33fz9gl8p.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdbpoxacgegg33fz9gl8p.jpg" alt="CLAUDE.md Is RAM, Skills Are Not Disk: The Four-Tier Memory Model for Claude Code" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Claude Code loads &lt;code&gt;CLAUDE.md&lt;/code&gt; in full at the start of every session and keeps it resident the whole time, so every line in it is a line you pay for on every turn, whether the task in front of you needs it or not. &lt;a href="https://dev.to/posts/claude-md-is-ram-not-disk/"&gt;CLAUDE.md Is RAM, Not Disk&lt;/a&gt; split that into two tiers to deal with it: &lt;code&gt;CLAUDE.md&lt;/code&gt; holds only what Claude needs every time, stack, commands, invariants; everything else moves to &lt;code&gt;docs/&lt;/code&gt;, which loads only when something points Claude there. Working memory stays small and constantly paid for. Long-term memory stays cheap because it's optional.&lt;/p&gt;

&lt;p&gt;I wasn't using custom skills when I wrote that post. Once I started, the obvious move was to file them under disk too: they're detail Claude doesn't need every turn, so surely they belong wherever the rest of the on-demand material lives. That reasoning is wrong, and it took checking how skills actually behave in context to see why. A skill is not a document Claude opens when curious. It's closer to a program that gets launched, and treating it like disk is how invariants end up sitting behind a trigger that might not fire.&lt;/p&gt;

&lt;p&gt;Here's the model with two more tiers than it had before: &lt;code&gt;CLAUDE.md&lt;/code&gt; is RAM, path-scoped rules are demand-paged RAM, skills are installed programs, and &lt;code&gt;docs/&lt;/code&gt; is disk. Each tier answers the same question differently: when does this load, and what happens to it after. Get that question right for a given piece of instruction and the tier picks itself.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Checked against Claude Code v2.1.212 on July 27, 2026. The listing and compaction budgets below are the kind of number that moves; verify them against the current docs before leaning on the exact figures.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the two-tier model couldn't see
&lt;/h2&gt;

&lt;p&gt;The original test was: does Claude need this every turn, or only sometimes? That question sorts &lt;code&gt;CLAUDE.md&lt;/code&gt; from &lt;code&gt;docs/&lt;/code&gt; correctly, but it quietly assumes there are only two ways for something to load: resident from the start, or resident only once Claude decides to open a file. Path-scoped rules and skills both load conditionally, and neither one is "Claude decides to open a file."&lt;/p&gt;

&lt;p&gt;A rule loads because a file path matched a glob. That's deterministic, the same way session start is deterministic, just scoped instead of constant. A skill loads because Claude matched a task description to a name in a list. That's probabilistic: it matches text, so it usually fires and sometimes does not. Collapsing those two into "sometimes" and filing both next to &lt;code&gt;docs/&lt;/code&gt; erases the one distinction that actually matters: whether the content is guaranteed to show up when you need it, or merely likely to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tier 1: RAM, unchanged
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; still works exactly the way the first post described it: loaded in full at session start, resident every turn after, re-read from disk and re-injected after &lt;code&gt;/compact&lt;/code&gt;. Adding three more tiers doesn't change what belongs here, stack, commands, the handful of invariants that must never break. It's still the only tier with no "when does this load" question to answer. The answer is always.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tier 2: demand-paged RAM, and a correction to the first post
&lt;/h2&gt;

&lt;p&gt;The nested &lt;code&gt;CLAUDE.md&lt;/code&gt; trick from the original post still works, but there's a cleaner mechanism for the same job. If you copied that trick into your own project, this is the point where I'd tell you to migrate it. &lt;code&gt;.claude/rules/*.md&lt;/code&gt; files with a &lt;code&gt;paths:&lt;/code&gt; glob in their frontmatter load into context the same way a nested &lt;code&gt;CLAUDE.md&lt;/code&gt; does, when Claude reads a file that matches. The docs say a nested &lt;code&gt;CLAUDE.md&lt;/code&gt; is not re-injected after &lt;code&gt;/compact&lt;/code&gt; and reloads the next time Claude touches that subdirectory. They don't say the same for rules, and my own testing suggests it behaves the same way.&lt;/p&gt;

&lt;p&gt;Leave off &lt;code&gt;paths:&lt;/code&gt; and none of this applies. A rule file without it loads at launch with the same priority as &lt;code&gt;.claude/CLAUDE.md&lt;/code&gt; itself, so &lt;code&gt;.claude/rules/testing.md&lt;/code&gt; with no frontmatter is tier 1 wearing a different folder, not tier 2. The mechanism doesn't decide the tier. The loading behavior does.&lt;/p&gt;

&lt;p&gt;What a scoped rule adds over the nested &lt;code&gt;CLAUDE.md&lt;/code&gt; it replaces: the glob cuts across the directory tree instead of being pinned to one folder, so "every &lt;code&gt;*.php&lt;/code&gt; file under &lt;code&gt;app/Domains&lt;/code&gt;, but not the tests next door" is one line, instead of a rule you cannot express. Every rule file also lives in one folder instead of being scattered one per directory, so reviewing what conditional context exists in a project is a single &lt;code&gt;ls&lt;/code&gt;, not a find across the tree. Same loading behavior as before, better shape:&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="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app/Domains/**/*.php"&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# Domain layer rules&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tier 3: installed programs
&lt;/h2&gt;

&lt;p&gt;A skill's name and description sit in a listing Claude can see from the start of the session, and that listing has its own budget: roughly 1% of the context window. When it overflows, Claude Code shortens descriptions starting with the skills you invoke least, so the ones you use constantly keep their full text and a skill you just added competes for space with everything else you've built. None of that costs much, which is the entire pitch: a project can carry thirty skills for a fraction of what thirty paragraphs in &lt;code&gt;CLAUDE.md&lt;/code&gt; would cost, as long as most of them don't fire in most sessions.&lt;/p&gt;

&lt;p&gt;The moment one does fire, that pitch stops applying. The full &lt;code&gt;SKILL.md&lt;/code&gt; body enters context and stays resident for the rest of the session, at the same per-turn cost as anything in &lt;code&gt;CLAUDE.md&lt;/code&gt;. Compaction treats it better than you'd expect but worse than the root file: the first 5,000 tokens of each invoked skill survive &lt;code&gt;/compact&lt;/code&gt;, shared across a 25,000 token combined budget, filled starting from whichever skill fired most recently. Invoke enough skills in one long session and the ones you called early get dropped entirely, quietly, with no warning that they're gone.&lt;/p&gt;

&lt;p&gt;Two things make a skill something other than either RAM or disk. It can bundle scripts that run without their code ever entering context, only the output does, which nothing in &lt;code&gt;CLAUDE.md&lt;/code&gt; or &lt;code&gt;docs/&lt;/code&gt; can do. And you can turn off its automatic trigger with &lt;code&gt;disable-model-invocation: true&lt;/code&gt;, so it only runs when you type &lt;code&gt;/name&lt;/code&gt;, trading the "maybe it fires" problem for "you have to remember to ask." That flag does more than disable auto-firing: it pulls the name and description out of the listing entirely, not just out of the trigger, so a skill set this way is the one case that costs nothing at all until you invoke it. That second point cuts both ways: a skill can also grant itself tool access through &lt;code&gt;allowed-tools&lt;/code&gt;, which makes a committed project skill something you review the way you'd review a settings file, not something you skim the way you'd skim a doc. Skills accept a &lt;code&gt;paths:&lt;/code&gt; glob too, the same format as rules, but it only narrows when Claude considers firing one automatically, so a scoped skill is filtered, not guaranteed.&lt;/p&gt;

&lt;p&gt;Skills are not disk, because disk is passive. A skill is an installed program with an entry in a lookup table, dormant until something calls it, and then it runs. That's also why the failure mode is specific. Putting a procedure behind a skill trigger is fine, because the cost of it not firing once is that Claude does the steps manually instead of running the script. Putting an invariant behind a skill trigger is not fine, because the cost of it not firing once is that the rule silently didn't apply, and a fuzzy description match is a bad place to gamble something that must always hold.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tier 4: disk, unchanged
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;docs/&lt;/code&gt; still works exactly the way the first post described it. &lt;code&gt;DESIGN.md&lt;/code&gt;, &lt;code&gt;PLAN.md&lt;/code&gt;, &lt;code&gt;DECISIONS.md&lt;/code&gt;, long prose that humans read too, loaded only when something points to it and only because Claude decided to follow the pointer. It's still the weakest guarantee of the four tiers, and that's correct: disk was always supposed to be the tier you fall back to for material that doesn't need to be certain, only available.&lt;/p&gt;

&lt;p&gt;One trap is worth closing here, since it's the most common way people think they're on disk when they aren't. &lt;code&gt;@docs/DESIGN.md&lt;/code&gt; inside a &lt;code&gt;CLAUDE.md&lt;/code&gt; is not a pointer, it's an import: the file is expanded and loaded into context at launch, same as everything else in the root file. A plain prose reference, &lt;code&gt;docs/DESIGN.md&lt;/code&gt;, no &lt;code&gt;@&lt;/code&gt;, is what actually stays on disk until Claude follows it. The first post used prose pointers throughout, which was the right call, more by luck than by design.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four tiers side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;Loaded when&lt;/th&gt;
&lt;th&gt;Resident cost&lt;/th&gt;
&lt;th&gt;Survives &lt;code&gt;/compact&lt;/code&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. RAM&lt;/td&gt;
&lt;td&gt;Root &lt;code&gt;CLAUDE.md&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Session start, in full&lt;/td&gt;
&lt;td&gt;Every turn&lt;/td&gt;
&lt;td&gt;Yes, re-read from disk and re-injected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Demand-paged RAM&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.claude/rules/*.md&lt;/code&gt; with &lt;code&gt;paths:&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Claude reads a file matching the glob&lt;/td&gt;
&lt;td&gt;Every turn after that, for the rest of the session&lt;/td&gt;
&lt;td&gt;No, lost until Claude reads a matching file again (inferred, not documented)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. Installed programs&lt;/td&gt;
&lt;td&gt;Skill in &lt;code&gt;.claude/skills/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Name and description always listed unless &lt;code&gt;disable-model-invocation&lt;/code&gt;; body loads on trigger&lt;/td&gt;
&lt;td&gt;Resident for the rest of the session once triggered&lt;/td&gt;
&lt;td&gt;First 5,000 tokens per skill, 25,000 combined, most recent first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. Disk&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;docs/*.md&lt;/code&gt; referenced by path (not &lt;code&gt;@&lt;/code&gt; imported)&lt;/td&gt;
&lt;td&gt;Only if Claude decides to read it&lt;/td&gt;
&lt;td&gt;Every turn after Claude reads it&lt;/td&gt;
&lt;td&gt;Whatever survives the summary&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read top to bottom and it's also a certainty gradient. Tier 1 is guaranteed. Tier 2 is guaranteed for the files it's scoped to. Tier 3 is probable, or certain only if you invoke it yourself. Tier 4 is optional. Deciding where a piece of instruction belongs is really deciding how much certainty it needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Applied to a Laravel and event sourcing project
&lt;/h2&gt;

&lt;p&gt;The starter repo from the first post had three tiers: root &lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;docs/&lt;/code&gt;, and a nested &lt;code&gt;CLAUDE.md&lt;/code&gt; inside &lt;code&gt;app/Domains&lt;/code&gt;. It now has all four:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;claude-code-laravel-starter/
├── CLAUDE.md                    # tier 1: stack, commands, invariants
├── README.md
├── .claude/
│   ├── rules/
│   │   └── domain.md            # tier 2: paths: ["app/Domains/**/*.php"]
│   └── skills/
│       └── README.md            # tier 3: how to install a project skill
├── docs/
│   ├── DESIGN.md                # tier 4: domain model, aggregates, events
│   ├── PLAN.md                  # tier 4: phased, plan-first implementation
│   └── DECISIONS.md             # tier 4: lightweight ADR log
└── app/
    └── Domains/                  # rules replace the nested CLAUDE.md that used to live here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;domain.md&lt;/code&gt; keeps the rule that used to live in the nested file: no framework imports in the domain layer, aggregates only record events, events are immutable and past tense. What changed is where it lives: one file in a folder you can review alongside every other conditional rule in the project, instead of a &lt;code&gt;CLAUDE.md&lt;/code&gt; you have to remember exists three directories down. &lt;code&gt;.claude/skills/&lt;/code&gt; doesn't ship a fake example skill; it documents how to install the real one, my &lt;a href="https://github.com/albertoarena/claude-laravel-event-sourcing" rel="noopener noreferrer"&gt;Laravel event sourcing skill&lt;/a&gt;, which designs and generates the aggregate, events, and projector for a new bounded context in conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four rules to take away
&lt;/h2&gt;

&lt;p&gt;If Claude needs it every turn, it goes in &lt;code&gt;CLAUDE.md&lt;/code&gt;. If it only matters inside files that match a pattern, it goes in a rule, not a nested &lt;code&gt;CLAUDE.md&lt;/code&gt;. If it's a procedure with steps, a script, or a template, it goes in a skill, and anything that shouldn't fire on its own gets &lt;code&gt;disable-model-invocation: true&lt;/code&gt;. If it's long enough that a human should read it too and Claude only needs it sometimes, it goes on disk.&lt;/p&gt;

&lt;p&gt;The two-tier version of this held up fine for a while. It just couldn't tell you where to put the thing that runs.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>productivity</category>
      <category>devtools</category>
    </item>
    <item>
      <title>There's no artisan schema:show, so I built one</title>
      <dc:creator>Alberto Arena</dc:creator>
      <pubDate>Fri, 24 Jul 2026 06:32:36 +0000</pubDate>
      <link>https://dev.to/alberto_arena_25a48484ed5/theres-no-artisan-schemashow-so-i-built-one-a2l</link>
      <guid>https://dev.to/alberto_arena_25a48484ed5/theres-no-artisan-schemashow-so-i-built-one-a2l</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwj0org21njz5fxc5vp6b.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwj0org21njz5fxc5vp6b.jpg" alt="Laravel Truss" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You join a project. Forty tables, half of them undocumented, and a &lt;code&gt;posts&lt;/code&gt; table that somehow relates to three other tables you've never heard of. Where do you start?&lt;/p&gt;

&lt;p&gt;&lt;code&gt;php artisan db&lt;/code&gt; gets you an interactive shell, and from there you're back to whatever dialect your database speaks: &lt;code&gt;.schema&lt;/code&gt; on SQLite, &lt;code&gt;DESCRIBE&lt;/code&gt; on MySQL, &lt;code&gt;\d&lt;/code&gt; on Postgres. Laravel's own &lt;code&gt;db:show&lt;/code&gt; and &lt;code&gt;db:table&lt;/code&gt; commands are a step up, an overview of your tables or one table's columns, indexes, and foreign keys, printed straight to the terminal. But they're still one table at a time, in text. Nothing shows you the shape of the thing: which tables actually connect, and how, across the whole schema. So you end up either opening a DB client through an SSH tunnel, or drawing the diagram yourself on a whiteboard from memory, which is exactly as reliable as it sounds.&lt;/p&gt;

&lt;p&gt;And that's the easy case, when you have shell access at all. On staging or production, where you actually need this the most because that's where the real schema drifted from the migrations, you often don't have a shell. Some organisations block SSH outright for compliance, which is exactly the situation I &lt;a href="https://albertoarena.it/posts/beyond-the-bastion-aws-ssm-laravel-artisan/" rel="noopener noreferrer"&gt;wrote about replacing with AWS Systems Manager&lt;/a&gt; for running Artisan commands. Even with a Run Command session in hand, that gets you a shell again, not a diagram.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://github.com/albertoarena/laravel-truss" rel="noopener noreferrer"&gt;Truss&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;Truss is a live database structure viewer for Laravel. It scans your actual schema, not your migration files, and renders it as a scrollable, zoomable ER diagram right inside your app.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live ER diagram&lt;/strong&gt;, rendered with Mermaid, of every table and how it connects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focus mode&lt;/strong&gt;: click a table to isolate it and its foreign-key neighbours, centred and highlighted, so you're not staring at forty tables to understand one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filter by table name&lt;/strong&gt;, and toggle native column types against Laravel-style labels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Map-style pan and zoom&lt;/strong&gt;, with auto-fit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structure only, always.&lt;/strong&gt; Truss reads tables, columns, keys, and indexes. It never queries a single row, which is the whole point of being able to run it somewhere that matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-contained&lt;/strong&gt;: Mermaid and fonts are vendored and served from the package, so it works offline and under a strict CSP. No CDN, nothing phoning home.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cached and automatic&lt;/strong&gt;: the snapshot rebuilds after migrations, so the diagram never goes stale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export the whole diagram&lt;/strong&gt; as PNG or SVG, exactly as it's currently filtered and focused, so it drops straight into a design doc or a PR description instead of a screenshot. Per-table exports as JSON or CSV are also available from each table's menu.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install it, visit &lt;code&gt;/truss&lt;/code&gt;, and you get the diagram. No config, no separate service to run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require albertoarena/laravel-truss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default it's only enabled locally. To use it on staging or production you explicitly enable it and gate access behind a &lt;code&gt;viewTruss&lt;/code&gt; authorization gate, so "safe to run in prod" doesn't mean "wide open in prod".&lt;/p&gt;

&lt;h2&gt;
  
  
  From the command line, too
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;db:show&lt;/code&gt; and &lt;code&gt;db:table&lt;/code&gt; get you close, but they're still one table at a time, in text. &lt;code&gt;php artisan truss:show&lt;/code&gt; prints that same cached, exclusion-filtered snapshot the diagram uses, as a table: one row per database table, with its column count and foreign-key count, the whole schema at once instead of one piece of it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan truss:show   &lt;span class="c"&gt;# the whole schema as a table, in your terminal&lt;/span&gt;
php artisan truss:open   &lt;span class="c"&gt;# or jump straight to the diagram&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;php artisan truss:open&lt;/code&gt; opens the dashboard in your default browser, honouring the route prefix and app URL. It prints the URL regardless, so it still works over a forwarded port on a headless host with no browser for it to open.&lt;/p&gt;

&lt;p&gt;Both commands live under a &lt;code&gt;truss:&lt;/code&gt; namespace rather than &lt;code&gt;schema:&lt;/code&gt;. A package squatting Laravel's own &lt;code&gt;schema:&lt;/code&gt; namespace is asking for a collision if the framework ever ships a &lt;code&gt;schema:show&lt;/code&gt; of its own, which is the joke in this post's title.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying it without installing anything
&lt;/h2&gt;

&lt;p&gt;If you want to poke at it before adding it to a project, there's a &lt;a href="https://albertoarena.github.io/laravel-truss/demo/" rel="noopener noreferrer"&gt;live demo&lt;/a&gt; running against a fictional schema, right in your browser. Pan, zoom, filter, focus a table, no install required.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it's held up
&lt;/h2&gt;

&lt;p&gt;I've dropped Truss into a few of my own Laravel projects since building it, including ones I hadn't touched in a while, and it's the fastest I've re-oriented myself in a schema I half-remembered. Clicking through foreign keys in focus mode beats reconstructing the relationships from migration file names, every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://albertoarena.github.io/laravel-truss" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; covers installation, the quick start, and authorization for non-local environments in more detail. It requires PHP 8.3+ and Laravel 12+.&lt;/p&gt;

&lt;p&gt;Feedback welcome, as always.&lt;/p&gt;

&lt;p&gt;Known dev.to gotcha from the plan notes: pasting long content into its textarea can silently drop a newline at a random spot and merge two lines (has happened to two code blocks before) — worth a scroll-through of both code fences before publishing.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>database</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Claude Code Routines: A Third Way Beyond /goal and /loop</title>
      <dc:creator>Alberto Arena</dc:creator>
      <pubDate>Mon, 20 Jul 2026 06:24:20 +0000</pubDate>
      <link>https://dev.to/alberto_arena_25a48484ed5/claude-code-routines-a-third-way-beyond-goal-and-loop-1h4m</link>
      <guid>https://dev.to/alberto_arena_25a48484ed5/claude-code-routines-a-third-way-beyond-goal-and-loop-1h4m</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs13i1q23v8d6cl2xstpc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs13i1q23v8d6cl2xstpc.jpg" alt="Claude Code Routines" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few months ago, Claude Code shipped &lt;a href="https://albertoarena.it/posts/goal-command-claude-code/" rel="noopener noreferrer"&gt;&lt;code&gt;/goal&lt;/code&gt;&lt;/a&gt;, a way to stop typing "keep going" every turn and let Claude work until a condition was actually met. That solved a real problem: staying in the loop with an open session so you didn't have to babysit it turn by turn. It didn't solve a different problem, the one where you want Claude to do something and you're not even at your laptop.&lt;/p&gt;

&lt;p&gt;That gap just closed. Anthropic added routines: saved configurations that run on Anthropic's own cloud infrastructure, triggered by a schedule, an API call, or a GitHub event, with no open session and no machine required. It's currently in research preview, so behavior and limits may still shift, but the shape of the feature is clear enough to be worth understanding now.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a routine actually is
&lt;/h2&gt;

&lt;p&gt;A routine bundles three things: a prompt, one or more GitHub repositories, and a set of connectors (Slack, Linear, and other MCP integrations). Package it once, attach a trigger, and it runs on its own from then on, cloning the repository fresh each time and executing as a full, unattended Claude Code session.&lt;/p&gt;

&lt;p&gt;There's no permission-mode picker and no approval prompts during a run. Whatever the environment and connectors allow, Claude can do without asking. That's the point (nobody's there to answer the prompt) but it also means the blast radius of a routine is defined entirely by what you scope it to, not by anything you can intervene on mid-run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating one
&lt;/h2&gt;

&lt;p&gt;The fastest way in is &lt;code&gt;/schedule&lt;/code&gt; (alias &lt;code&gt;/routines&lt;/code&gt;), run inside any CLI session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/schedule daily PR review at 9am
&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;/schedule in 2 weeks, open a cleanup PR that removes the feature flag
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude asks follow-up questions about the repository, the prompt, and the cadence, then saves the routine to your claude.ai account. &lt;code&gt;/schedule&lt;/code&gt; only sets up schedule triggers though: API and GitHub triggers need the web form at claude.ai/code/routines, where you can also review or edit anything the CLI created. &lt;code&gt;/schedule list&lt;/code&gt;, &lt;code&gt;/schedule update&lt;/code&gt;, and &lt;code&gt;/schedule run&lt;/code&gt; manage existing routines from the terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three ways to trigger one
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Scheduled.&lt;/strong&gt; A recurring cadence (hourly, nightly, weekly) or a single run at a specific future time. The minimum interval is one hour, so this isn't a replacement for tight polling, it's for the kind of check that genuinely only needs to happen once a day or once a week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API.&lt;/strong&gt; Each routine gets its own endpoint and bearer token. POST to it and a new session starts, optionally carrying a &lt;code&gt;text&lt;/code&gt; field with run-specific context, an alert body, a failing log line, whatever the caller wants to hand off. That text arrives wrapped in a block that labels it as untrusted data, so a routine's prompt has to explicitly opt in to acting on it. It's a deliberate guard: anyone holding a leaked token can send text, but they can't use it to redirect the routine into doing something the saved prompt never asked for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub events.&lt;/strong&gt; Pull request and release events, with filters on author, title, branch, labels, draft state, and merge state. A routine can watch for &lt;code&gt;pull_request.opened&lt;/code&gt; on a specific base branch and run your team's own review checklist before a human ever looks at the diff.&lt;/p&gt;

&lt;p&gt;A single routine can combine more than one trigger. A PR-review routine might run nightly as a backstop, fire from a deploy script, and also react to every new pull request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Routines vs /goal vs /loop
&lt;/h2&gt;

&lt;p&gt;The three now cover genuinely different problems, not three flavors of the same one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/loop&lt;/code&gt;&lt;/strong&gt; re-runs a prompt on an interval, inside a session you have open, on your own machine. No completion check, just repetition. Good for polling a build or babysitting a PR while you're around.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/goal&lt;/code&gt;&lt;/strong&gt; keeps a session working, on your machine, until a separate evaluator model confirms a condition holds. Good for a bounded task you kicked off and want finished properly before you look again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Routines&lt;/strong&gt; run on Anthropic's infrastructure, with no session and no machine required, triggered by time, an API call, or a GitHub event. Good for anything that should happen whether or not you're at your desk.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The practical filter: if the automation needs to survive you closing your laptop, it has to be a routine. Neither &lt;code&gt;/loop&lt;/code&gt; nor &lt;code&gt;/goal&lt;/code&gt; does, both stop the moment the session ends.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where routines shine
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Backlog maintenance.&lt;/strong&gt; A schedule trigger set to &lt;code&gt;0 22 * * *&lt;/code&gt; (nightly at 10pm) reads issues opened since the last run, applies labels, assigns owners by code area, and posts a summary to a &lt;code&gt;#triage&lt;/code&gt; Slack channel before the team's day starts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alert triage.&lt;/strong&gt; A monitoring tool posts to the routine's API endpoint when an error threshold trips. The routine investigates the payload, correlates it with recent commits, and opens a draft PR with a proposed fix, so on-call reviews a diff instead of starting from a blank terminal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bespoke code review.&lt;/strong&gt; A GitHub trigger fires on every &lt;code&gt;pull_request.opened&lt;/code&gt;, applies your team's own checklist, and leaves inline comments for the mechanical stuff so human reviewers can focus on design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-repo porting.&lt;/strong&gt; A GitHub trigger on merged PRs in one SDK repository ports the same change to a parallel SDK in another language, keeping two libraries in step without someone re-implementing each change by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it gets tricky
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Everything a routine does carries your identity.&lt;/strong&gt; Commits and pull requests use your GitHub account. Slack messages and Linear tickets use your linked accounts. There's no separate "bot" identity to fall back on, so a routine with too many connectors attached can do a surprising amount in your name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No approval gate means the environment is the only safety net.&lt;/strong&gt; Network access, environment variables, and which connectors are included are configured once, up front, and apply to every run after that. Scope them to exactly what the routine needs, not to whatever happens to already be connected to your account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Green doesn't mean the task succeeded.&lt;/strong&gt; A green status means the session started and exited without an infrastructure error, nothing more. Whether Claude actually did what the prompt asked is a separate question you still have to check by reading the run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Branch pushes are restricted by default.&lt;/strong&gt; Claude can only push to &lt;code&gt;claude/&lt;/code&gt;-prefixed branches unless you explicitly allow unrestricted pushes for a repository. It's a sensible default, but worth knowing before a routine's first PR shows up on an unexpected branch name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is a research preview.&lt;/strong&gt; The API surface ships under a dated beta header, and Anthropic has already said behavior, limits, and field shapes may change. Don't build anything load-bearing on top of it just yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runs share your regular usage quota, plus a separate daily cap.&lt;/strong&gt; Routine runs draw down the same subscription usage as an interactive session, on top of an account-wide daily limit on how many routine runs can start. Set up several nightly routines and it's easy to assume each gets its own budget. It doesn't, so check your remaining runs at claude.ai/code/routines before you find out the hard way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical advice
&lt;/h2&gt;

&lt;p&gt;Start with a schedule trigger before reaching for API or GitHub triggers. It's the easiest to reason about and the easiest to pause if something looks wrong.&lt;/p&gt;

&lt;p&gt;Strip the connector list down to what the routine actually touches. Every connector you leave attached is something Claude can use without asking, on every run, indefinitely.&lt;/p&gt;

&lt;p&gt;Treat routine output the same way you'd treat a CI pipeline: review the diff, don't trust the status light. A run that "completed successfully" and a run that "did the right thing" are not the same claim.&lt;/p&gt;

&lt;p&gt;If a routine acts on external input (an alert body, a webhook payload), write the prompt to reference that payload explicitly. Otherwise it just sits there as inert context and the routine won't act on it at all, which is the safe failure mode, but worth knowing in advance rather than discovering it during an incident.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thought
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/goal&lt;/code&gt; solved the problem of staying in a session without re-prompting it. Routines solve the problem of not needing a session at all. Pick the one that matches the problem you actually have, and for the first time, none of them requires bending one tool to do a different job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://code.claude.com/docs/en/routines" rel="noopener noreferrer"&gt;Automate work with routines&lt;/a&gt;: official Anthropic documentation for routines, covering creation, triggers, connectors, and limits.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://code.claude.com/docs/en/scheduled-tasks" rel="noopener noreferrer"&gt;Run prompts on a schedule&lt;/a&gt;: official documentation for &lt;code&gt;/loop&lt;/code&gt; and session-scoped scheduled tasks, including the comparison against routines and Desktop scheduled tasks.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://code.claude.com/docs/en/goal" rel="noopener noreferrer"&gt;Keep Claude working toward a goal&lt;/a&gt;: official documentation for the &lt;code&gt;/goal&lt;/code&gt; command.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://albertoarena.it/posts/goal-command-claude-code/" rel="noopener noreferrer"&gt;Claude Code /goal vs /loop: Stop Typing, Keep Going&lt;/a&gt;: the earlier post this one builds on.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>claudecode</category>
      <category>productivity</category>
      <category>automation</category>
    </item>
    <item>
      <title>10,000 downloads</title>
      <dc:creator>Alberto Arena</dc:creator>
      <pubDate>Sun, 19 Jul 2026 14:18:33 +0000</pubDate>
      <link>https://dev.to/alberto_arena_25a48484ed5/10000-downloads-274h</link>
      <guid>https://dev.to/alberto_arena_25a48484ed5/10000-downloads-274h</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5is9px37wf7n5qhrimll.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5is9px37wf7n5qhrimll.jpg" alt="laravel-event-sourcing-generator" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I built it to solve a specific friction point: setting up a Spatie event-sourcing domain meant writing a lot of the same files every time — aggregates, events, projectors, reactors, read models. The generator takes a migration or a quick description of your model and scaffolds all of it in one artisan command. It's not a framework addition, just a time-saver.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require &lt;span class="nt"&gt;--dev&lt;/span&gt; albertoarena/laravel-event-sourcing-generator
php artisan event-sourcing:generate Post
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;10k is more than I expected when I published it. The package has a narrow focus and a niche audience — Laravel developers who've already bought into event sourcing — so seeing it spread this far is genuinely surprising.&lt;/p&gt;

&lt;p&gt;The project didn't stop at the generator. Earlier this year I built a &lt;a href="https://github.com/albertoarena/claude-laravel-event-sourcing" rel="noopener noreferrer"&gt;Claude Code skill&lt;/a&gt; (&lt;a href="https://albertoarena.it/posts/ai-laravel-event-sourcing/" rel="noopener noreferrer"&gt;write-up&lt;/a&gt;) that adds a design conversation before the code generation, so you work through the domain boundaries before anything gets written. More recently, the &lt;a href="https://github.com/albertoarena/filament-event-sourcing" rel="noopener noreferrer"&gt;filament-event-sourcing&lt;/a&gt; plugin (&lt;a href="https://albertoarena.it/posts/introducing-filament-event-sourcing/" rel="noopener noreferrer"&gt;write-up&lt;/a&gt;) closes the gap between Filament's admin panel and your aggregates. The generator is still the foundation, but these fill in the parts it was never meant to handle.&lt;/p&gt;

&lt;p&gt;Thanks to everyone who's used it, filed issues, or left feedback. The package is still actively maintained and the best place for questions is the &lt;a href="https://github.com/albertoarena/laravel-event-sourcing-generator/issues" rel="noopener noreferrer"&gt;GitHub issues&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>php</category>
      <category>eventsourcing</category>
      <category>spatie</category>
      <category>laravel</category>
    </item>
    <item>
      <title>Create a domain using Spatie event sourcing</title>
      <dc:creator>Alberto Arena</dc:creator>
      <pubDate>Sun, 19 Jul 2026 06:57:04 +0000</pubDate>
      <link>https://dev.to/alberto_arena_25a48484ed5/create-a-domain-using-spatie-event-sourcing-nee</link>
      <guid>https://dev.to/alberto_arena_25a48484ed5/create-a-domain-using-spatie-event-sourcing-nee</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6ao2kv63e3pc7w8blhha.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6ao2kv63e3pc7w8blhha.jpg" alt="Create a domain using Spatie event sourcing" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Event sourcing is a powerful pattern for tracking changes to application state, offering a robust way to manage domain-driven design in Laravel. However, setting up an event-sourcing domain can be time-consuming and repetitive, especially when following best practices.&lt;/p&gt;

&lt;p&gt;To streamline this process, I've created a Laravel package that automates the creation of event-sourcing domains using Spatie's event sourcing library. With just one artisan command, you can generate all the boilerplate code needed for your domain, including aggregates, events, and projectors.&lt;/p&gt;

&lt;p&gt;This package empowers developers to focus on their business logic rather than spending time on repetitive setup tasks. Whether you're building financial systems, audit trails, or complex applications that require detailed state tracking, this package can help you get started faster and with less hassle.&lt;/p&gt;

&lt;p&gt;Here's a sneak peek at how simple it is to use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:event-sourcing-domain MODEL &lt;span class="nt"&gt;--domain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;DOMAIN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this blog post, I'll walk you through how to use this package, explain its key features, and provide practical examples to help you integrate event sourcing into your Laravel projects efficiently.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/albertoarena/laravel-event-sourcing-generator" rel="noopener noreferrer"&gt;Check out the package on GitHub!&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the command
&lt;/h2&gt;

&lt;p&gt;The primary command provided by this package is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:event-sourcing-domain MODEL &lt;span class="nt"&gt;--domain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;DOMAIN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command generates the necessary structure and files for a complete event-sourcing domain in your Laravel project. Let's break it down:&lt;/p&gt;

&lt;h3&gt;
  
  
  Parameters
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;MODEL&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;This represents the core entity or aggregate root for your domain.&lt;/li&gt;
&lt;li&gt;Example: If you're building an e-commerce application, a MODEL could be Order, Product, or Customer.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;--domain=DOMAIN&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;This defines the domain or bounded context where your model operates.&lt;/li&gt;
&lt;li&gt;Example: For an Order model, you might place it in a domain like Sales or Inventory.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Command Usage
&lt;/h3&gt;

&lt;p&gt;Here's an example of how to use this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:event-sourcing-domain Order &lt;span class="nt"&gt;--domain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Sales
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this will generate the following structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/
└── Domain/
    └── Sales/
        ├── Actions/
        │   ├── CreateOrder.php
        │   ├── DeleteOrder.php
        │   └── UpdateOrder.php
        ├── Aggregates/
        │   └── OrderAggregate.php
        ├── Events/
        │   ├── OrderCreated.php
        │   ├── OrderDeleted.php
        │   └── OrderUpdated.php
        ├── Projections/
        │   └── Order.php
        └──Projectors/
           └── OrderProjector.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example workflow
&lt;/h2&gt;

&lt;p&gt;To demonstrate the power of this package, let's walk through an example of setting up a domain for managing Orders within a Sales domain in your Laravel application.&lt;/p&gt;

&lt;p&gt;We'll assume that a migration already exists.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// database/migrations/2024_12_20_121314_create_orders_table.php&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Migration&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;**&lt;/span&gt;
     &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nc"&gt;Run&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;
     &lt;span class="o"&gt;*/&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'orders'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'customer_email'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'customer_name'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'total_amount'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&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="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'items'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;timestamps&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="c1"&gt;// etc.&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1: Generate the Event-Sourcing Domain
&lt;/h3&gt;

&lt;p&gt;Run the &lt;code&gt;make:event-sourcing-domain&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:event-sourcing-domain Order &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--domain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Sales &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--aggregate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this will generate the following structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;app/
└── Domain/
    └── Sales/
        ├── Aggregates/
        │   └── OrderAggregate.php
        ├── DataTrasnferObjects/
        │   └── OrderData.php
        ├── Events/
        │   ├── OrderCreated.php
        │   ├── OrderUpdated.php
        │   └── OrderDeleted.php
        ├── Projections/
        │   └── Order.php
        └── Projectors/
            └── OrderProjector.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Use it
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Domain\Sales\Actions\CreateOrder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Domain\Sales\DataTransferObjects\OrderData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Domain\Sales\Projections\Order&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;# This will create a record in 'orders' table, using projector OrderProjector&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CreateOrder&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OrderData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;customer_email&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'john@acme.org'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;customer_name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'John Doe'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;total_amount&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;110.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s1"&gt;'id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;12345&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Whiskey bottle'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'quantity'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'price'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;55.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;# Retrieve record&lt;/span&gt;
&lt;span class="nv"&gt;$order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'john@acme.org'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Advanced Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Generate PHPUnit tests
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/albertoarena/laravel-event-sourcing-generator/blob/main/docs/unit-tests.md" rel="noopener noreferrer"&gt;Read full documentation here&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Another interesting feature is the ability to generate automatically PHPUnit tests for the model.&lt;/p&gt;

&lt;p&gt;Run the &lt;code&gt;make:event-sourcing-domain&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:event-sourcing-domain Order &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--domain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Sales &lt;span class="nt"&gt;--unit-test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Generate notifications
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/albertoarena/laravel-event-sourcing-generator/blob/main/docs/advanced-options.md#generate-notifications" rel="noopener noreferrer"&gt;Read full documentation here&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The package allows to generate notifications (mail, Slack and Teams) with an option.&lt;/p&gt;

&lt;p&gt;Run the &lt;code&gt;make:event-sourcing-domain&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:event-sourcing-domain Order &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--domain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Sales &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--aggregate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--notifications&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mail,slack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this will generate the following structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/
└── Domain/
    └── Sales/
        ├── Aggregates/
        │   └── OrderAggregate.php
        ├── DataTrasnferObjects/
        │   └── OrderData.php
        ├── Events/
        │   ├── OrderCreated.php
        │   ├── OrderUpdated.php
        │   └── OrderDeleted.php
        ├── Notifications/
        │   ├── Concerns/
        │   │   └── HasDataAsArray
        │   ├── OrderCreated.php
        │   ├── OrderUpdated.php
        │   └── OrderDeleted.php
        ├── Projections/
        │   └── Order.php
        └── Projectors/
            └── OrderProjector.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Some Bluepoint column types are not yet supported. You can find the &lt;a href="https://github.com/albertoarena/laravel-event-sourcing-generator/blob/main/docs/migrations.md#unsupported-column-types" rel="noopener noreferrer"&gt;list at this link&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;del&gt;Update migrations cannot yet be used, but only create migrations.&lt;/del&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;em&gt;Hey, this is available now!&lt;/em&gt;&lt;/strong&gt; (*)&lt;/p&gt;

&lt;p&gt;&lt;del&gt;Finally, the package will need to be properly tested with PHP 8.3 and released for that version.&lt;/del&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;em&gt;I did it!&lt;/em&gt;&lt;/strong&gt;  (*)&lt;/p&gt;

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

&lt;p&gt;Event sourcing is a transformative approach to building applications with robust state management and a clear audit trail. By leveraging the Spatie event-sourcing library, combined with this Laravel package, you can drastically reduce the time spent setting up event-sourcing domains while ensuring consistency and best practices.&lt;/p&gt;

&lt;p&gt;With a single artisan command, you can generate all the necessary files and structure for your domain, empowering you to focus on building the unique aspects of your application.&lt;/p&gt;

&lt;p&gt;If you're ready to streamline your development workflow and dive into event sourcing, try the package today:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/albertoarena/laravel-event-sourcing-generator" rel="noopener noreferrer"&gt;Check out the package on GitHub!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to star the repository, report issues, or contribute to its development. Your feedback and contributions are invaluable for improving the package and making it even more powerful for Laravel developers worldwide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;p&gt;On December 31, 2024, I have added support to PHP 8.3.&lt;/p&gt;

&lt;p&gt;On February 16, 2025, I have added support for update migrations. 👉 &lt;a href="https://github.com/albertoarena/laravel-event-sourcing-generator/blob/main/docs/migrations.md#generate-a-domain-using-update-migration" rel="noopener noreferrer"&gt;Read documentation here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On June 30, 2026, this package crossed 10,000 downloads on Packagist — &lt;a href="https://albertoarena.it/posts/laravel-event-sourcing-generator-10k/" rel="noopener noreferrer"&gt;read the retrospective&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>php</category>
      <category>eventsourcing</category>
      <category>spatie</category>
      <category>laravel</category>
    </item>
    <item>
      <title>Claude Code /goal vs /loop: Stop Typing, Keep Going</title>
      <dc:creator>Alberto Arena</dc:creator>
      <pubDate>Sun, 19 Jul 2026 05:48:21 +0000</pubDate>
      <link>https://dev.to/alberto_arena_25a48484ed5/claude-code-goal-vs-loop-stop-typing-keep-going-3aj4</link>
      <guid>https://dev.to/alberto_arena_25a48484ed5/claude-code-goal-vs-loop-stop-typing-keep-going-3aj4</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3u9cx81giqetbfbuudzy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3u9cx81giqetbfbuudzy.jpg" alt="Claude Code /goal vs /loop: Stop Typing, Keep Going" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've spent any real time with Claude Code, you've been through this loop. You give it a meaningful task, something like refactoring a service layer or wiring up a new set of endpoints. It works for one turn, produces solid output, and then hands control back to you. So you type "keep going." It does another round. Stops again. You re-prompt. Another round. Another stop. An hour later you've typed "keep going" more times than you've typed actual code, and the irony of manually babysitting your autonomous coding agent starts to sting.&lt;/p&gt;

&lt;p&gt;Anthropic shipped a fix for that, and it's five letters long.&lt;/p&gt;

&lt;h2&gt;
  
  
  What /goal does
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;/goal&lt;/code&gt; command sets a completion condition for your session. After each turn, a small and fast evaluator model (Haiku by default) checks whether the condition has been met. If not, Claude starts another turn. If yes, the goal clears and you get a summary with time, turns, and tokens spent.&lt;/p&gt;

&lt;p&gt;The syntax is dead simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/goal all tests in test/payments pass and lint is clean
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Setting the goal counts as the first turn. You don't need to send a separate prompt.&lt;/p&gt;

&lt;p&gt;The key design decision is separation. There are two models involved: one does the coding, and a different one decides whether the coding is finished. The evaluator reads the conversation transcript and makes a binary call: done or not done. If not done, it also returns a short reason that becomes guidance for the next turn. So if Claude thinks it's finished but three tests are still failing, the evaluator catches the gap and sends Claude back to work.&lt;/p&gt;

&lt;p&gt;OpenAI shipped the same concept in Codex CLI around the same time, under the same name. Both implementations trace back to the "Ralph loop" pattern that the agentic coding community had been hand-rolling with bash scripts for months. Now it's built in. No custom scripting, no wrapper, no cron.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it shines
&lt;/h2&gt;

&lt;p&gt;The best use cases share a common trait: the end state is verifiable and Claude can prove it within the conversation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test-driven migrations.&lt;/strong&gt; "Migrate all v1 API calls to v2 and make sure every test passes." Claude reads the code, changes call sites one by one, runs the suite after each batch, and keeps going until green. This is the sweet spot. The goal condition maps directly to a command exit code. The evaluator has something concrete to check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Issue backlog cleanup.&lt;/strong&gt; "Close every open TODO in the /legacy folder." Claude works through them systematically. Each one gets resolved, the evaluator counts what's left, and the loop continues until the queue is empty.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File restructuring.&lt;/strong&gt; "Split this 3,000-line file into focused modules, each under 300 lines, with all imports still resolving." Measurable, bounded, verifiable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Design doc implementation.&lt;/strong&gt; "Implement the acceptance criteria in SPEC.md and run the integration suite." If your spec is well-written, Claude has a checklist to work through and a way to prove completion.&lt;/p&gt;

&lt;p&gt;In all these cases you're basically saying: here's the finish line, don't come back until you've crossed it. And it works surprisingly well when the finish line is clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it gets tricky
&lt;/h2&gt;

&lt;p&gt;The evaluator only reads the conversation transcript. It doesn't run commands, browse the filesystem, or verify anything independently. This means the quality of the loop is entirely dependent on how well your condition can be proven through Claude's own output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vague goals fail quietly.&lt;/strong&gt; "Improve the authentication system" gives Claude no way to know when to stop. It might keep refactoring forever, or it might stop after one change and declare victory. Both outcomes are plausible and neither is useful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Long sessions drift.&lt;/strong&gt; Claude manages a finite context window. During extended runs it compresses earlier turns into summaries. If your task spans dozens of turns, nuances from the beginning may be lost by the end. A 14-hour session sounds impressive in a blog post, but in practice the quality of the last hour may not match the first. Anthropic recommends including a turn or time limit directly in the condition ("or stop after 20 turns") to bound the loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Token costs add up fast.&lt;/strong&gt; Every turn costs main-model tokens plus a small Haiku evaluation. One developer noted that the token use can scale dramatically compared to a supervised session. If you're on a metered plan, a runaway goal can burn through your budget before you finish your coffee.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scope creep is real.&lt;/strong&gt; Claude might notice a related bug while working on your goal and decide to fix it. That sounds helpful until it touches a file owned by another team or introduces a change you didn't ask for. Always include constraints in your condition: "do not modify files outside /src/payments" is not optional, it's a safety net.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It doesn't replace judgment.&lt;/strong&gt; &lt;code&gt;/goal&lt;/code&gt; is for execution, not for design decisions. If the task involves ambiguity ("should this be a queue or a synchronous call?"), Claude will pick an answer and keep going. You might not agree with the answer it picked, and by the time you check, there are fifteen files built on top of that assumption.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to write a good condition
&lt;/h2&gt;

&lt;p&gt;Anthropic's docs recommend structuring conditions around three elements: a measurable outcome, an explicit verification step, and boundaries around what should not be touched. That's good advice. In practice it means your condition should read like a checklist, not a wish.&lt;/p&gt;

&lt;p&gt;A condition like this works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/goal all tests in test/payments pass, lint exits 0, and no file outside src/payments is modified
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A condition like this doesn't:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/goal make the code better
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The condition can be up to 4,000 characters, so you have room. Use it to be specific. Include the exact commands that should be run, the directories that are in scope, and the files that are off limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  /goal vs /loop vs regular prompting
&lt;/h2&gt;

&lt;p&gt;Claude Code now has three ways to keep a session alive without constant prompting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Regular prompting&lt;/strong&gt; is your default. You send a message, Claude responds, control returns to you. Best for exploratory work, design conversations, and anything where you want to review each step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;/loop&lt;/code&gt;&lt;/strong&gt; re-runs a prompt on a time interval. It doesn't check a condition. Useful for monitoring or repeated checks ("run the test suite every 10 minutes and report failures"), less useful for convergent tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;/goal&lt;/code&gt;&lt;/strong&gt; runs until a condition holds. The evaluator is what makes it different: completion is decided by a fresh model, not by the one doing the work. Combined with auto mode (which approves tool calls within a turn), you get fully unattended execution.&lt;/p&gt;

&lt;p&gt;The simplest way to choose: if the trigger is time, use &lt;code&gt;/loop&lt;/code&gt;. If the trigger is a verifiable outcome, use &lt;code&gt;/goal&lt;/code&gt;. If you want to review each step before the next one happens, just keep prompting normally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical advice
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Always work on a branch.&lt;/strong&gt; Before starting a &lt;code&gt;/goal&lt;/code&gt; session, commit your current state. If Claude goes sideways, you can &lt;code&gt;git diff&lt;/code&gt; to see what happened and &lt;code&gt;git checkout&lt;/code&gt; to undo it. This isn't optional.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start small.&lt;/strong&gt; Your first &lt;code&gt;/goal&lt;/code&gt; should not be a full codebase migration. Pick something contained: a single module, a single test file, a clearly bounded task. Watch how Claude executes it. Learn what works before you scale up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Include a turn limit.&lt;/strong&gt; "or stop after 15 turns" is cheap insurance against runaway loops. You can always set a new goal after reviewing progress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Review the diff like any PR.&lt;/strong&gt; Autonomous work is still code that ships. Don't treat &lt;code&gt;/goal&lt;/code&gt; output as automatically correct. Read the changes, run the tests yourself, and apply the same review standards you would for any contributor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pair it with CLAUDE.md.&lt;/strong&gt; If your project has conventions, patterns, or off-limits areas that aren't obvious from the code, document them in your CLAUDE.md. Claude reads it at the start of every session and will respect those constraints during a &lt;code&gt;/goal&lt;/code&gt; loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thought
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/goal&lt;/code&gt; is the most significant workflow change in Claude Code since auto mode. It formalises something developers were already doing with duct-tape scripts and "keep going" prompts, and it does it with an evaluator layer that's genuinely clever.&lt;/p&gt;

&lt;p&gt;But it's a power tool, not magic. The better your condition, the better the result. The vaguer your condition, the more creative Claude gets with the interpretation, and "creative" is not always what you want from an autonomous agent editing your production code.&lt;/p&gt;

&lt;p&gt;Use it for execution when the thinking is done. Keep the thinking for yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://code.claude.com/docs/en/goal" rel="noopener noreferrer"&gt;Keep Claude working toward a goal&lt;/a&gt; — Official Anthropic documentation for the &lt;code&gt;/goal&lt;/code&gt; command.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://medium.com/@jason.croucher/claude-code-goal-a-field-guide-with-games-f6f3b617ce5b" rel="noopener noreferrer"&gt;Claude Code /goal: A Field Guide with Games&lt;/a&gt; — Jason Croucher's hands-on walkthrough with practical examples and real session logs.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ralphable.com/blog/codex-goal-command-ralph-loop-openai-built-in-autonomous-coding-agent-2026" rel="noopener noreferrer"&gt;Codex /goal: OpenAI's Built-in Ralph Loop&lt;/a&gt; — Deep dive into the Codex CLI implementation and the Ralph loop history.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://findskill.ai/blog/claude-code-goal-command/" rel="noopener noreferrer"&gt;Claude Code /goal: Set a Finish Line, Walk Away&lt;/a&gt; — FindSkill.ai's breakdown of the feature with cost and token considerations.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>developertools</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
