<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:blog="https://jonesrussell.github.io/blog/ns"><channel><title>Spec-Kitty on Web Developer Blog</title><link>https://jonesrussell.github.io/blog/tags/spec-kitty/</link><description>Recent content in Spec-Kitty on Web Developer Blog</description><image><title>Web Developer Blog</title><url>https://jonesrussell.github.io/blog/images/og-default.png</url><link>https://jonesrussell.github.io/blog/images/og-default.png</link></image><generator>Hugo -- 0.161.1</generator><language>en-us</language><lastBuildDate>Mon, 11 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://jonesrussell.github.io/blog/tags/spec-kitty/feed.xml" rel="self" type="application/rss+xml"/><item><title>Bumping a PHP monorepo to 8.5: the mechanics</title><link>https://jonesrussell.github.io/blog/waaseyaa-php-version-bump-monorepo/</link><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://jonesrussell.github.io/blog/waaseyaa-php-version-bump-monorepo/</guid><category>ai</category><blog:series>waaseyaa-php-8-5-upgrade</blog:series><blog:tag>php</blog:tag><blog:tag>waaseyaa</blog:tag><blog:tag>monorepo</blog:tag><blog:tag>spec-kitty</blog:tag><description>Sixty-seven packages, one CI matrix, one PHPStan target. What it takes to actually move the floor.</description><content:encoded><![CDATA[<p>Ahnii!</p>
<p>This is the first of three posts about taking Waaseyaa to PHP 8.5. This one is about the mechanics: how a coordinated version bump across a 67-package monorepo actually happens. The next two cover the deprecation sweep that came with it and the features we deliberately did not adopt.</p>
<blockquote>
<p><strong>Context:</strong> Waaseyaa is the open-source PHP framework I have been writing about. Mission: <code>php-8-5-upgrade-01KR8DN2</code>. Shipped as PR <a href="https://github.com/waaseyaa/waaseyaa/pull/1406">#1406</a>, merge commit <a href="https://github.com/waaseyaa/waaseyaa/commit/e0f8cb570"><code>e0f8cb57</code></a>. Released in alpha.176.</p>
</blockquote>
<h2 id="the-starting-state">The starting state</h2>
<p>Before the bump, Waaseyaa required PHP 8.4. Sixty-six first-party <code>composer.json</code> files, all aligned on <code>&gt;=8.4</code>. Plus a skeleton package, which is a template artifact and is kept at the lowest reasonable floor on purpose.</p>
<p>CI ran a single PHP version. PHPStan was pinned to a matching <code>phpVersion</code>. The floor was tight and consistent. That alignment is what makes a bump cheap. The expensive version of this story is the one where every package picks its own minimum and you have to negotiate sixty-six exceptions.</p>
<h2 id="mission-shape">Mission shape</h2>
<p>The mission split into five work packages plus a closing one:</p>
<ul>
<li><strong>WP01.</strong> Constraint bump, CI, Docker, lockfile, PHPStan pin, docs, governance charter touch.</li>
<li><strong>WP02.</strong> 8.5 deprecation sweep.</li>
<li><strong>WP03.</strong> Adopt <code>#[\NoDiscard]</code> on critical surfaces.</li>
<li><strong>WP04.</strong> Targeted <code>array_find()</code> adoption.</li>
<li><strong>WP05.</strong> PHP-CS-Fixer migration rules.</li>
<li><strong>WP06.</strong> CHANGELOG and verification.</li>
</ul>
<p>WP01 is the only one that touches the floor. Everything after is feature work that becomes available because the floor moved. Splitting it this way matters: if WP01 lands clean, the rest can land in any order without coupling.</p>
<h2 id="what-wp01-actually-changed">What WP01 actually changed</h2>
<p>The mechanical surface of a floor bump is smaller than people expect. From the merge:</p>
<ul>
<li><strong>66 first-party <code>composer.json</code> files</strong> updated from <code>&gt;=8.4</code> to <code>&gt;=8.5</code>.</li>
<li><strong>3 GitHub Actions workflows</strong> repinned to <code>php-version: '8.5'</code>: <code>ci.yml</code>, <code>skeleton-smoke.yml</code>, <code>release-cut.yml</code>. Ten total occurrences of the string <code>'8.5'</code>.</li>
<li><strong><code>phpstan.neon</code></strong> updated: <code>phpVersion: 80500</code>.</li>
<li><strong>Lockfile</strong> regenerated against 8.5.</li>
</ul>
<p>That is the bump. Everything else in the mission is downstream of those four artifacts moving in lockstep.</p>
<p>The reason the surface is small is that Waaseyaa has hard gates that already enforce alignment. There is a <code>bin/check-composer-policy</code> script that fails CI if any package drifts from the root constraint. There is a <code>bin/check-package-layers</code> script that fails if the dependency direction inverts. There is a <code>tools/drift-detector.sh</code> that fails if docs lag the code. The floor is one number defended in many places.</p>
<h2 id="the-verification-surface">The verification surface</h2>
<p>For a bump to be safe, every hard gate has to be green on the new floor. Waaseyaa&rsquo;s full gate list for this mission:</p>
<ul>
<li><code>composer phpstan</code> (root level + package level)</li>
<li><code>vendor/bin/phpunit</code></li>
<li><code>composer cs-check</code></li>
<li><code>bin/check-composer-policy</code></li>
<li><code>bin/check-package-layers</code></li>
<li><code>bin/audit-dead-code</code></li>
<li><code>tools/drift-detector.sh</code></li>
</ul>
<p>At merge time the test suite was 7,497 unit tests, 18,118 assertions, 0 deprecations, 2 expected skips. That is the number to trust. Not because tests prove a version is fine, but because the test corpus is dense enough that deprecation warnings would surface.</p>
<h2 id="why-split-it-into-work-packages-at-all">Why split it into work packages at all</h2>
<p>This is the part worth paying attention to if you maintain a PHP monorepo. The actual diff for a version bump is small. You could do it in one PR with one commit. People do.</p>
<p>The cost of doing it that way is that the diff conflates four different kinds of change:</p>
<ol>
<li>The floor moves (a policy change).</li>
<li>Deprecations get removed (a behavior change).</li>
<li>New features get adopted (a style change).</li>
<li>New tooling gets wired (a configuration change).</li>
</ol>
<p>When all four land in one squash commit, the next person to touch any of them cannot read the rationale. Six months later, someone reverts a <code>#[\NoDiscard]</code> attribute thinking it is part of the floor bump, and now the floor bump cannot be reverted cleanly either.</p>
<p>The work package structure makes each kind of change auditable on its own terms. WP02 is removable without affecting WP01. WP04 can be reverted without touching WP03. The mission directory is the persistent record of why each was done.</p>
<p>That is the same point I made about the <a href="/blog/spec-kitty-mission-lifecycle/">Spec Kitty mission lifecycle</a> post: the output of any mission is replaceable. The trail is not.</p>
<h2 id="what-the-next-posts-cover">What the next posts cover</h2>
<p>Post 2 in this series digs into the deprecation sweep: what 8.5 surfaced, where it was hiding in the codebase, and how the sweep got from 34 warnings to 0.</p>
<p>Post 3 covers the features we deliberately did not adopt. Property hooks. The pipe operator. Broader <code>array_find()</code>. The argument is that restraint is part of the upgrade, not absent from it.</p>
<p>Baamaapii</p>
]]></content:encoded></item><item><title>Spec Kitty mission lifecycle: a domain modeling pass through Giiken</title><link>https://jonesrussell.github.io/blog/spec-kitty-mission-lifecycle/</link><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://jonesrussell.github.io/blog/spec-kitty-mission-lifecycle/</guid><category>ai</category><blog:tag>spec-kitty</blog:tag><blog:tag>giiken</blog:tag><blog:tag>waaseyaa</blog:tag><blog:tag>ai</blog:tag><description>What a full Spec Kitty mission actually looks like end to end: spec, plan, tasks, implement, review, merge.</description><content:encoded><![CDATA[<p>Ahnii!</p>
<p>A lot of agent frameworks promise &ldquo;end to end&rdquo; workflows. Most of them stop at &ldquo;generate a plan and hope.&rdquo; Spec Kitty is different. It runs a real mission through a state machine, with artifacts on disk and gates between phases. This post walks one of those missions, <code>giiken-domain-modeling-01KR2HKT</code>, from spec to merge.</p>
<blockquote>
<p><strong>Context:</strong> Giiken is the community knowledge service built on Waaseyaa. The mission did discovery and docs for its domain model. Real commit: <a href="https://github.com/waaseyaa/giiken/commit/5b2328bf330b73bc1d999999bcc7cae02e2b1b6f"><code>waaseyaa/giiken@5b2328b</code></a>.</p>
</blockquote>
<h2 id="what-a-mission-actually-is">What &ldquo;a mission&rdquo; actually is</h2>
<p>A Spec Kitty mission is a directory under <code>kitty-specs/</code>, named with a slug and an ULID. After this mission landed, that directory looked like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>kitty-specs/giiken-domain-modeling-01KR2HKT/
</span></span><span style="display:flex;"><span>  spec.md
</span></span><span style="display:flex;"><span>  plan.md
</span></span><span style="display:flex;"><span>  research.md
</span></span><span style="display:flex;"><span>  data-model.md
</span></span><span style="display:flex;"><span>  meta.json
</span></span><span style="display:flex;"><span>  status.json
</span></span><span style="display:flex;"><span>  status.events.jsonl
</span></span><span style="display:flex;"><span>  mission-events.jsonl
</span></span><span style="display:flex;"><span>  checklists/
</span></span><span style="display:flex;"><span>    requirements.md
</span></span><span style="display:flex;"><span>  research/
</span></span><span style="display:flex;"><span>    evidence-log.csv
</span></span><span style="display:flex;"><span>    source-register.csv
</span></span></code></pre></div><p>That is not a generated artifact dump. Each file has a role in the state machine. <code>spec.md</code> is the contract. <code>plan.md</code> is the chosen approach. <code>research.md</code> plus the CSVs are the evidence trail. <code>status.json</code> and the two <code>.jsonl</code> files are the lane state and the audit log. The checklist is a hard gate, not a suggestion.</p>
<h2 id="the-phases">The phases</h2>
<p>The mission moved through these phases. Each one writes an artifact and emits a status event.</p>
<ol>
<li><strong>Specify.</strong> Compile a <code>spec.md</code> from the mission brief. Requirements get checklisted. Ambiguity gets surfaced before code touches the repo.</li>
<li><strong>Plan.</strong> Choose an approach in <code>plan.md</code>. Inputs from spec. Output is the shape of the work.</li>
<li><strong>Tasks.</strong> Break the plan into work packages (WPs). Each WP is independent enough to assign and review on its own.</li>
<li><strong>Implement.</strong> Each WP runs through implement and review until approved. State transitions go through the orchestrator, not by hand.</li>
<li><strong>Review.</strong> Per WP, against the spec. Reviewers can reject with structured feedback.</li>
<li><strong>Merge.</strong> Once every WP is approved, the mission squash-merges and the events log records the terminal state.</li>
</ol>
<p>The thing that makes this different from a long prompt is that every transition is gated. You can&rsquo;t move a WP to <code>approved</code> without a passing review. You can&rsquo;t merge with WPs still in flight. The agent is constrained to the shape of the state machine.</p>
<h2 id="what-this-mission-actually-produced">What this mission actually produced</h2>
<p>Beyond the kitty-specs directory, the merge commit added two architecture documents to the Giiken repo:</p>
<ul>
<li><code>docs/architecture/domain-model.md</code></li>
<li><code>docs/architecture/lifecycle.md</code></li>
</ul>
<p>And the implementation work in WP01 and WP02 left the data model migration-aligned, with PHPUnit and Vitest both green at merge time. Thirteen files in one squash commit, all traceable back to the spec.</p>
<p>The point is the trail. A reader six months from now can open <code>kitty-specs/giiken-domain-modeling-01KR2HKT/</code> and see: what was asked for, what was chosen, what evidence informed it, what got built, and which checks passed. That is a working memory you can hand to the next agent or the next human.</p>
<h2 id="why-this-matters-more-than-the-output">Why this matters more than the output</h2>
<p>The output of this mission is fine. Useful, even. But the output is replaceable. The trail is not.</p>
<p>If you have been around agent workflows for any length of time, you know the failure mode: an AI session ends, the context evaporates, and the next session has to reconstruct everything from the code. Spec Kitty inverts that. The mission directory <strong>is</strong> the persistent context. The next agent picks up the spec and the checklist, not a chat log.</p>
<p>That is the lifecycle proof: not &ldquo;an agent shipped code,&rdquo; but &ldquo;an agent moved through a structured workflow that another agent or human can audit, resume, or extend.&rdquo;</p>
<h2 id="try-it">Try it</h2>
<p>If you want to see one of these missions in your own repo, the easiest path is to install Spec Kitty and run <code>spec-kitty next --agent &lt;name&gt;</code> on a small scope. Pick something with a clear question, not a vague refactor. Discovery missions like this one are a good first try.</p>
<p>The commit for the mission described here is <a href="https://github.com/waaseyaa/giiken/commit/5b2328b"><code>waaseyaa/giiken@5b2328b</code></a>. The full mission directory is in that repo at <code>kitty-specs/giiken-domain-modeling-01KR2HKT/</code>.</p>
<p>Baamaapii</p>
]]></content:encoded></item></channel></rss>