Skip to content

Releases

v0.17.0

Choose a tag to compare

@kvnwolf kvnwolf released this 08 Sep 16:27

🏷️ v0.17.0 β€” every worker gets a name tag

This one was learned in the field, not designed at a desk. A real /dobby:execute run hit a wall on its very first dispatch, the operator patched the protocol by hand mid-build, and /dobby:learn turned that session into the kit. The headline: the build loop's workers are now addressable per task, and you can watch the whole plan move, step by step, while it runs.

πŸ“› Per-task worker names

The dispatch protocol used to tell the Architect to name every worker dobby:<role>. The Agent tool's name field rejects a : outright, so that first dispatch failed with an InputValidationError every single time, and even if it hadn't, one name per role collides the moment two tasks run in parallel. Workers are now dispatched as test-author-t<id> / implementor-t<id> / qa-t<id> (the subagent_type stays dobby:<role>), every dispatch opens with the task's roster so a worker knows exactly who its siblings are, and a task's three workers are dispatched once: every later round reaches the same name by SendMessage, which resumes the agent with its context intact. No more fresh workers re-reading the world mid-task. dobby spec lint now also checks that a task's # cell is a legal name fragment, so an address can never be malformed by construction.

πŸ“Š A live status table after every step

Ask any operator watching a multi-task run what they want and it is this: after every worker verdict and every fix round, the Architect prints one row per task, one column per step, one emoji per cell (βšͺ not started Β· πŸ”„ in progress Β· ❌ failed, in fix Β· βœ… passed Β· β€” n/a), with the transitions spelled out so two runs never show the same state two different ways. The closing summary table is unchanged; this is the running view that was missing.

πŸ” Fix rounds close themselves

A round used to have a hole in it: QA reported a defect, the implementor fixed it, and nobody said who woke QA back up. Now the fixer messages back by name with the round number. And a corrected test contract always returns through the implementor's Exit gate before QA resumes, because QA never runs the suite, so a contract fix that skipped the gate would have been an unproven done. The test-author agent gained SendMessage to hold up its end of that conversation.

πŸ—‚οΈ Affected areas that match how apps are actually built

spec lint rejected TanStack-style $param route directories as "not a real path", and accepted a not-yet-existing area only when its direct parent already existed. The field workaround was to name whole directories, which made every task overlap and serialised the entire plan. Both rules are relaxed: $ is a legal path character, and a planned path just needs some existing ancestor below the repo root. task-decomposition.md now says it plainly: name files, not directories, from the first draft. Parallelism is back.

⚠️ Two things this release asks of you

  • Reload before your next execute. test-author's tools changed, and Claude Code registers agent tools at session startup. Run /reload-plugins (or open a fresh session), or your test-authors will be mute.
  • A spec can go red. dobby spec lint now rejects a # cell that is not a short [A-Za-z0-9_-] token. Renumber the tasks 1, 2, 3… and it passes.

The full walkthrough lives in plugin/skills/upgrade/references/v0.17.md, and /dobby:upgrade walks it for you.

⬆️ Upgrade

bun update @kvnwolf/dobby

Then /reload-plugins, and run bunx dobby spec lint on any live STATE.md before your next /dobby:execute.

v0.16.0

Choose a tag to compare

@kvnwolf kvnwolf released this 04 Sep 21:34

🏠 The worktree is yours. dobby works wherever you already stand.

Until now /dobby:scope opened its own worktree for every goal β€” a kit-named branch under .claude/worktrees/, guarded by a scope preflight that computed slug collisions and nesting before anything ran. That ceremony duplicated what every host already does on its own β€” claude --worktree, Claude Desktop, t3 code, your IDE, a bare git worktree add β€” and it made a plain checkout with one goal per branch a second-class citizen: the lifecycle simply refused to run there.

v0.16.0 drops the ceremony. Scope grounds the goal where the session already stands, and finish cleans up exactly what it finds there β€” a worktree somebody opened, or a branch on a plain checkout. Same skills, same bunx dobby up/down, one path fewer to maintain. This very release was built that way: a branch on the main checkout, no worktree, closed by the new finish. It worked the first time.


🧭 /dobby:scope no longer creates anything

Run it in a worktree you opened β€” with any tool β€” or on a branch in a plain checkout. It writes STATE.md where you are, and brings the run up with bunx dobby up exactly as before (the setup phase, .worktreeinclude re-materialization, the two-step bring-up β€” all unchanged). The goal's slug is basename(workroot) everywhere: the Neon branch, the cmux panes, the workspace rename. There is no nesting guard left because there is nothing left to nest β€” whoever made the worktree owns that judgment, not dobby.

🧹 /dobby:finish is symmetric: it tears down what it stands in

The merge gate is the same as ever β€” pr watch says merge-ready, you pick "Merge & finish", the squash is pinned to the validated SHA. After bunx dobby down --json, teardown now branches on where the session stands:

  • In a linked worktree (whoever made it): finish offers to remove it and its branch β€” native ExitWorktree first, so the cwd comes back to the main checkout; on refusal, raw git from mainRoot, never from inside the directory it's about to delete.
  • On a plain checkout: there's no worktree to remove. Finish switches back to the goal's base branch, force-deletes the goal's branch (-D, because a squash-merge leaves a tip git will always call "unmerged"), and pulls.

Which branch is "back"? That single field took five review rounds, and the answer is now a decision, not a heuristic. Finish returns you to the branch the goal's PR was merged into β€” its baseRefName, reported as defaultBranch with defaultBranchSource: "pr-base". A goal opened against develop belongs back on develop; dragging you to a repository-wide trunk would move you away from where you were working. Only a branch that never had a PR falls through to the git cascade β€” a validated origin/HEAD (a dangling symref counts as unset), then origin/main/origin/master, then local main/master, then null β€” and null stops with the three commands for you to run, rather than guessing at a branch the checkout can't reach. If the base exists only on the remote, finish creates it with git switch --track. The reasoning lives in ADR-0033 so the next reviewer finds the decision, not the question.

πŸ” dobby finish --preflight reports where you stand

The payload is rebuilt around the current workroot: inWorktree, worktreePath, mainRoot, branch, defaultBranch + defaultBranchSource, pr (state/mergedAt/url), dirty (count + files, untracked included), dobbyInstalled β€” now probed at the workroot, not the main root, so a worktree missing its install can't hide behind a healthy main checkout β€” branchDeleteSafe, reasons[], and the unchanged verdict set (safe / blocked / confirm-required). Nothing in it removes anything; the skill branches on it and asks you at every destructive gate.


⚠️ Breaking changes β€” read before you upgrade

/dobby:scope opens no worktree. Want the goal isolated? Open one yourself first β€” claude --worktree, or git worktree add from the main checkout β€” or just work on a branch. If your CLAUDE.md tells scope to expect or create a worktree, drop that line.

dobby scope preflight is gone. The command, its collision/nesting detection and its suggestedSlug are removed with no replacement. A collision is now whatever your host or git reports.

dobby finish --preflight changed shape. --slug and candidates[] are gone (there is no kit-assigned slug to disambiguate). mode (same-session/orphan) and removeMechanism (ExitWorktree/raw-git) are replaced by inWorktree, worktreePath and mainRoot β€” the caller derives the removal path from those. defaultBranch and defaultBranchSource are new. Anything scripting against the old keys needs the new payload.

Finish on a plain checkout may stop. When no base branch can be resolved (defaultBranch: null), finish leaves the PR merged and the run torn down, prints git switch <trunk> / git branch -D <branch> / git pull, and ends. That's on purpose β€” it will not guess.


πŸ“¦ Upgrading

bun update @kvnwolf/dobby

/dobby:upgrade walks this note (v0.16.md) if you're jumping several versions. The decision and the two rejected alternatives β€” keep-behind-a-flag, and never-touch-worktrees β€” are in ADR-0033.

v0.15.0

Choose a tag to compare

@kvnwolf kvnwolf released this 04 Sep 00:15

🧭 dobby stops driving your environment and starts telling the model how to.

v0.14.0 put the host-specific mechanics behind one seam β€” open a cmux pane, spawn a detached process, open a browser, rename the workspace, tear it all down. That worked exactly as long as a CLI could control the host. Claude Desktop and t3 code drive their surfaces through MCP tools that only the model can call, so on those hosts the seam had nothing to execute and no way to say so.

v0.15.0 turns every adapter into a catalogue of instructions. dobby still executes what only it can execute β€” the setup phase, the liveness probe, pidfile registration, Neon branches, your setup[]/teardown[] extras. Everything host-shaped it now describes, per topic, and the model carries out. One shape everywhere, and two new hosts for free.


🧭 dobby instructions <topic> β€” the catalogue, readable on its own

Four topics: start, stop, browser, rename. Ask for one and you get the instruction for the environment you're actually in, as text or --json ({environment, topic, applies, text}). A topic that doesn't apply β€” rename on a plain terminal, which has no workspace to rename β€” answers applies: false with exit 0. That's a valid answer, not an error; you never have to know the environment/topic matrix to avoid a legitimate red. An unknown topic exits 1 and names the four.

πŸ–₯️ Four adapters, one file each

cmux, terminal, t3-code, claude-desktop, under cli/src/adapters/. Detection is an ordered cascade with terminal as the floor: CMUX_WORKSPACE_ID β†’ cmux; CLAUDE_CODE_ENTRYPOINT ∈ {claude-desktop, claude-desktop-3p, local-agent} β†’ Desktop; __CFBundleIdentifier starting with com.t3tools.t3code β†’ t3 code; anything else β†’ terminal, whose instructions (a background Bash, claude-in-chrome falling back to curl) work on any host. A new SDK host you've never heard of degrades instead of breaking.

The t3 code adapter was written against tool schemas loaded live in a t3 code session and verified against the vendor's source (v0.0.38). Its browser guide follows the vendor's own order β€” preview_status first, so the model reuses a tab instead of opening a second one β€” and names all 14 mcp__t3-code__preview_* tools. The Claude Desktop adapter is written from a field study and marked unverified in code until someone runs it against a live Desktop; public docs confirm the entrypoint values and the server name Claude Browser, not its method names. The study itself now lives in docs/field-studies/, so the adapter's provenance survives.

πŸ” up hands you what to do next β€” and does it in two steps

bunx dobby up --json no longer starts the server. It runs setup, probes the devUrl once, and returns live plus instructions[] β€” rename under cmux, start when nothing answers. You carry them out, run up again, and it sees the registered start in flight, waits in its liveness loop, and comes back live: true. A returned instruction is ok: true; it is the normal path, not a failure, and /dobby:scope's abort-or-degrade gate now fires only on real failures.

That two-step protocol is written once, in plugin/skills/execute/references/bring-up.md, and cited by every skill that used to promise "up ensures the run is up" β€” eight of them. If the contract changes again, one file changes.

bunx dobby down --json is new and symmetric: {cmux, instructions, ok, reason, slug, workroot}, with a stop instruction naming the exact panes to close when there are any.

πŸͺͺ dobby dev owns its pidfile β€” and survived six review rounds proving it

bunx dobby dev now registers itself in .dobby/dev.pid on startup, refuses a live twin (already running (pid N) β€” dobby down stops it, exit 1, nothing spawned), and clears it on teardown β€” so down finds the process no matter who launched it: the model, you by hand, or nobody. Greptile then spent six rounds on that registry, and it earned its keep: registration is an atomic O_EXCL create; a stale file is reclaimed by atomic rename with re-classification of what was actually captured; an ownership probe that can't answer (a failing ps, unparseable timing) never deletes and never reclaims β€” inconclusive is not dead; and down sweeps the dev.pid.stale.* sidecars a failed restore leaves behind. What's deliberately still open β€” a live registration stranded in a sidecar during a three-process race, visible to up only after down β€” is recorded in ADR-0032 as accepted residual risk, with the alternative that was rejected and why.

One more thing from that review, worth saying plainly: a repository path containing $(…) executed when the cmux start instruction was pasted into a shell. Every dynamic argument on every cmux command line is now single-quoted, nested where it has to nest, and the tests prove it by running the emitted lines through sh against a recording stub.

πŸ§ͺ CI runs the real dev now

The real-dev tests used to pass on CI by accident β€” portless wants sudo for port 443 and dies without a TTY, and the old test only waited for the pidfile to exist, which it did for an instant. They now run against a portless on an ephemeral unprivileged port with CI=1, and wait for the app to actually start.


⚠️ Breaking changes β€” read before you upgrade

dobby env --json no longer carries browserGuide. Anything that read it gets undefined. The guide moved to dobby instructions browser --json; dobby:qa already reads it there and now declares the three MCP tool families (claude-in-chrome, Claude_Browser, t3-code).

up no longer starts the dev server. It returns instructions[] and live. Any prompt or CLAUDE.md of yours that says "bunx dobby up ensures the run is up" is stale β€” point it at bring-up.md's two steps.

dev-start-failed is gone from reason; neon-branch-failed is new. A failed start is now diagnosed by the model that ran the command and can see its stderr β€” more than the old boolean ever told you.

dobby dev exits 1 when a live registered twin exists. dobby down stops it.

The Environment interface is reshaped β€” ensureRunSurface, ensureBrowserSurface, closeSurfaces, renameWorkspace and browserGuide are gone; what's left is id, cmux, discoverPanes and instruction(topic, ctx). Only relevant if you wrote an adapter.

Restart your session after upgrading. The dobby:qa agent's tools: frontmatter changed, and Claude Code pins the agent registry at startup.

Known limits. t3 code detection is macOS-only (the bundle id is an Info.plist artefact; t3 code injects no variable of its own β€” on Linux/Windows it falls to terminal and loses its browser guide). The Claude Desktop adapter is unverified against a live Desktop. .claude/launch.json is deliberately neither read nor written: dobby owns the devUrl model through portless.


πŸ“¦ Upgrading

bun update @kvnwolf/dobby

Then restart your Claude Code session before the next /dobby:execute. /dobby:upgrade walks the full v0.15 note if you're jumping several versions.

The decision, the four rejected alternatives and the accepted residual risk are in ADR-0032; ADR-0031's vendored cmux protocol is unchanged in content and changed in channel.

v0.14.0

Choose a tag to compare

@kvnwolf kvnwolf released this 21 Aug 02:00

🧦 The build loop stopped being a script.

Until now, /dobby:execute handed your whole plan to a native Claude Workflow: a JavaScript program that sequenced waves, counted retries and classified failures. It worked, and then a real run showed what it cost β€” 15 tasks, 1h43m, 1.75M tokens, 3 finished. One task ran out of its two attempts and the script switched off ten dependents in a cascade. Ten of that run's verdicts were environment β€” a dead browser β€” and each one burned an attempt exactly like a genuine bug would.

v0.14.0 takes the script out. The Architect now dispatches named subagents itself, and they talk to each other.


πŸ” Tasks start when they're ready, not when a batch says so

Waves are gone. A task begins the moment its own dependencies are done β€” no barrier waiting for a group to finish. Two exceptions, both deliberate: a destructive task runs alone, and two tasks whose Affected areas overlap never run at once (compared as real, canonical repository paths, which dobby spec lint now enforces).

And when a task dies, only its genuine dependents stop. Everything independent keeps going. No more five empty waves printing ⊘.

πŸ’¬ Workers now message each other

This is the one that changes how a fix feels. When QA finds a defect, it no longer ends the round so a fresh implementor can re-read the task, the spec and the code from nothing. It writes directly to the implementor that still has all of it loaded β€” and to the test-author when the test contract itself is the problem, described as behaviour only, never implementation, so the tests stay honest.

QA numbers every message it sends ("round 2 of 5") so the count lives in the text instead of somebody's memory. Five rounds, then it stops and reports. An environment failure goes to the Architect and doesn't spend a round β€” a dead browser is not a bug in your code.

🧱 Quality split into three layers, one owner each

Nothing told the old verifier what its job was, so it grabbed the widest thing available: it ran the entire quality gate and then discarded the lint findings by hand, and triaged a 3130-test suite to work out which reds were even its own.

Now:

  • The edit hook applies Biome's safe fixes and reports type errors β€” scoped to the file you just edited. On TypeScript 7's native compiler that costs about 0.15 s, so a type error surfaces as you write it instead of ambushing you at commit.
  • The implementor runs the full gate itself before handing off β€” the Exit gate, serialised so two of them never judge each other's half-written code.
  • QA proves behaviour and nothing else: the browser where there's an app, exercising the artefact for real where there isn't.
  • The PR review judges the finished change, tests included.

Tests deliberately did not move into the hook. vitest related pulls in 24 of 29 suites and ~48 s from any file in a densely-imported repo β€” and since the test-author writes the contract before implementation, a task's tests are red by design the whole time. Type checking moved; the suite stayed.

🎯 No more hand-written KNOWN-RED lists

Tasks used to carry prose telling agents which failures to ignore. One agent cloned main into /tmp just to be sure. Now a green baseline records what was already failing before the work began β€” by test identity, not by file β€” and the gate reports only what newly broke. A file with duplicate test names is marked ambiguous and exempts nothing, and says so, because a gate that guesses is worse than one that shouts.

🌐 QA stops rediscovering how to drive a browser

The cmux browser protocol now ships inside the CLI, with its provenance recorded, and dobby env hands QA the instructions for the environment it's actually in. No lookup, no reconstruction from memory.

πŸ”Œ And the ground for what's next

Environment coupling moved behind an adapter seam (cli/src/environment.ts) with two real implementations β€” cmux and plain terminal. Four scattered CMUX_WORKSPACE_ID reads became one. Supporting another host runner is now writing an adapter instead of editing the CLI. (Refs #27 β€” the Claude Desktop adapter is still ahead.)


⚠️ Breaking changes β€” read before you upgrade

dobby:verifier is now dobby:qa. This is the one that will bite. Claude Code pins its agent registry at session startup, so a session you already had open keeps the old one and dobby:qa won't resolve. Run /reload-plugins, or start a fresh session β€” and on a host without that command, a fresh session is the only way through. If /dobby:execute reports an unknown agent type right after upgrading, this is almost certainly why.

The edit hook now reports type errors, not just Biome findings. Expect to see more at edit time β€” that's the point, and it's scoped to the file you touched.

The implementor now runs the gate itself before handing off, inverting the old rule that forbade it from running any check.

dobby env no longer reports workflowRecipe. Agent model and effort live in each agent's frontmatter, full stop. dobby build-plan no longer emits waves[][] β€” it returns tasks with their dependsOn and destructive flags.


πŸ“¦ Upgrading

bun update @kvnwolf/dobby

Then restart your Claude Code session (or /reload-plugins) before running /dobby:execute.

/dobby:upgrade walks the full v0.14 note if you're jumping several versions.

Three architecture decisions came out of this one, if you want the reasoning: ADR-0029 (subagent dispatch, superseding ADR-0023), ADR-0030 (the three-layer quality model), and ADR-0031 (vendoring the browser protocol). ADR-0029 also records the residual risks that were accepted rather than hidden β€” worth reading if you run plans with many parallel tasks.

v0.13.1

Choose a tag to compare

@kvnwolf kvnwolf released this 17 Aug 02:54

🧹 The trim sweep can actually finish now

/dobby:trim-context had a humiliating secret: point it at a real repository and it would never finish. A field run against a 948-file codebase reached 1.2% coverage and could not write a single line of its ledger. Not slowly β€” never. Three rules in the sweep contract guaranteed it, and this release replaces all three with what actually worked when the same repository was swept by hand: βˆ’263,238 bytes across 165 files, gate green, zero executable lines touched.

πŸ“Š Judgment, not inventory

The old contract demanded one inventory row per eligible unit. On that repo, that meant 24,113 rows β€” the entire budget spent writing a table before a single judgment call happened.

Now the inventory is one row per file, ranked by comment-byte weight, and per-unit decisions happen inside the lot that touches the file. The Tree-sitter extractor stays exactly where it was (it was never the bottleneck β€” it ran once, cheaply, in seconds), but it is now the targeting input that ranks files into lots rather than the source of an exhaustive census.

βœ… Approve once, then walk away

The old contract wanted every batch approved before any worker wrote a byte. At repository scale that gate can never clear β€” you'd be clicking approvals until the heat death of the universe.

Now you approve scope and aggressiveness once, up front: which tracks run, how aggressive, what's off-limits. Lots then run unattended. The sweep comes back to you only when the policy needs to change, or something blocks. Go make coffee.

πŸ’Ύ Progress that survives

The worst of the three: a single parse-error voided the entire sweep. 43 unparseable .sql migrations permanently vetoed coverage genuinely earned on ~900 other files, and every rerun started from zero.

Coverage is now incremental and checkpointed per lot. A file that couldn't be resolved simply carries no sub-key β€” which already meant "not covered", so there's no schema change β€” and the savings report names it with its reason. An interruption, a blocked lot, an unparseable migration: each costs only itself. Everything already verified stays verified.

The honesty invariant is untouched, and it's the whole point: the ledger never claims coverage it doesn't have. What changed is that missing coverage no longer erases real coverage.

πŸ”’ Nothing gets certified without being validated

Four rounds of adversarial review chased one invariant through this design, and each round found it hiding a level deeper. All four were real:

  • Comment lots could skip semantic review whenever the mechanical diff came back clean β€” but deleting a biome-ignore, a licence header or a security warning changes no executable line either. The exemption is gone: every lot gets the cheap mechanical proof and an independent semantic review.
  • The checkpoint writer mutated the canonical ledger before validation. Now it stages to .dobby/sweeps.json.pending, the reviewer validates that staging file alone, and only the coordinator promotes it.
  • /dobby:anti-slop shares that same ledger file β€” so it got the same staged-then-promoted ordering. Nothing else about it changed.
  • Nothing bound a checkpoint to the lot that just passed, so a stale staging file could be promoted as someone else's coverage. The reviewer now verifies the increment's paths match the lot's file set exactly.

🎯 The recipe that worked, encoded

Lots of 6-8 files by weight (a 47-file lot exhausted one implementor at 13 files, 430K tokens, 257 tool calls). One track per brief β€” comment-trim or prose densification, never both, after a mixed brief produced a proud βˆ’186 bytes. Levers carried forward between lots, worth a steady ~βˆ’12% once discovered. And every brief now states its expected yield (~5% on comments, 12-14% on prose) with "leaving a file untouched is a correct outcome" said out loud, because an agent measured on bytes removed will find bytes to remove.

⚠️ One thing to know

rulesVersion moves from human-text-v1 to human-text-v2 β€” the preservation rules genuinely changed. If you somehow have an existing sweep ledger, its entries no longer certify, and the next run re-sweeps from scratch. In practice nobody does: writing a ledger is precisely what the old contract could never manage. πŸ™ƒ

πŸ“¦ Upgrading

bun update @kvnwolf/dobby

The CLI itself has no changes this release β€” everything above lives in the plugin, which autoUpdate carries to you from the marketplace. Bump the devDependency to keep the two surfaces in step.

Worth saying plainly: this is methodology, validated by 1,676 tests and four review rounds, but nobody has yet run the new sweep against a large repository end to end. That's the real test. If you point it at something big, the interesting question is whether it now reaches ledger finality β€” and the savings report will tell you exactly what it couldn't cover.

v0.13.0

Choose a tag to compare

@kvnwolf kvnwolf released this 14 Aug 02:51

Your repository has been writing to your model's context window this whole time, and nobody has been reading the bill. 🧾

v0.13.0 adds two sweeps that read it for you β€” and they start by counting, not by guessing.

πŸ” /dobby:trim-context β€” find out what your comments actually cost

Point it at a repository and the first thing it does is boring on purpose: it enumerates every tracked file and parses the comments out of them with real grammars. No regex, no vibes. On one production app that census came back with 24,113 comments across 1.83 MB β€” roughly 434,000 tokens sitting in code that every agent reads past on its way to the actual logic.

Only after that does any judgment happen. The skill proposes what to cut, in cost order, and waits for you: text that restates what the code plainly says goes, and the load-bearing stuff stays. Shebangs stay. biome-ignore directives stay. The comment explaining why you round half-up because the tax authority rejects banker's rounding β€” that one stays, because deleting it is how someone reintroduces the bug in eight months. Generated files are never touched at all.

It owns comments exclusively. No other sweep in the kit will go near them.

βœ‚οΈ /dobby:anti-slop β€” the voice pass, judged in context

The other sweep asks a different question: does this prose fabricate significance? Throat-clearing openers, the "it's not just X β€” it's Y" reversal that distinguishes nothing, bold bullet triads that define each term with itself.

It judges contextually and in any language, and that word is doing real work. Measuring this very repository is what settled the design: leverage and harness sit on every banned-word list on the internet, and both are domain terms in our own glossary. A skill that banned words by occurrence would have started its career by mangling the architecture vocabulary. So it doesn't ban anything β€” every finding names the pattern, quotes the exact text, and offers the smallest fix that works. It never claims a human or a model wrote something, and it never touches code comments.

Its taxonomy adapts Peter Yang's no-ai-slop, MIT attribution intact.

πŸ“’ Both sweeps remember what they already read

Coverage lives in a tracked .dobby/sweeps.json, keyed per file and per skill, hashed against exact final bytes. Your first run is a full sweep; every run after that only looks at what actually changed. Neither sweep can certify the other's work.

Everything is approval-gated, written by disjoint workers, and independently reviewed before any coverage is recorded. No new CLI command, config key, check, hook, or gate β€” these are inference, not enforcement, and your quality gate stays exactly as mechanical as it was.

πŸ› What the smoke tests caught

Both skills were driven end to end against throwaway fixtures before shipping, and that found two things static review had waved through. The ledger would never have become tracked in any real consumer, because .dobby/ is ignored β€” every run would have redone the full sweep, forever. And the two skills had incompatible ledger shapes, so whichever wrote second would have made the first fail closed. Both fixed here. The external reviewer then caught a third: an unbounded worker fan-out that could blow past the concurrency cap. Also fixed.

Worth saying plainly, because it's the useful part: none of these were found by reading the diff.

⚠️ One honest limitation

The bundled SQL grammar can't parse Postgres RLS or ACL statements, and Tree-sitter's error recovery poisons the whole file when it trips β€” so on a Supabase-style migrations directory, expect a large chunk of .sql to come back uncounted (43 of 58 files in the repo we measured). We evaluated the alternative grammar and rejected it: no WASM prebuild, identical failures when compiled by hand, and a lost provenance chain.

It's declared in the contract, in PROVENANCE.md, and in the extractor's own output. Coverage fails closed rather than pretending.

πŸ“¦ Upgrade

bun update @kvnwolf/dobby

Nothing is required of you β€” both skills are opt-in and user-invoked. When you want both, run /dobby:trim-context first, then /dobby:anti-slop. And be ready for the first sweep of a mature repository to propose more than you expect. That's the point.

v0.12.1

Choose a tag to compare

@kvnwolf kvnwolf released this 12 Aug 00:44

🩹 The cask release that couldn't read its own version

v0.12.0 shipped Homebrew cask releases. It just couldn't finish one β€” because the pack gate asked Tauri for a DMG and then went looking for an app bundle Tauri had never been told to keep. This release makes that whole path actually work.

πŸ”¨ The pack gate now builds what it inspects

The version gate's whole job is paranoia: before anything is tagged, notarized or pushed, it opens the artifact you just built and asks it what version it thinks it is. That answer lives in the .app's Info.plist β€” but the build only requested --bundles dmg, so the .app was a leftover from whatever happened to be on disk before, or nothing at all.

The build now asks for --bundles app,dmg. The .app supplies the embedded version the gate reads; the DMG is still the one artifact that ships. Same gate, same guarantees β€” except now it's inspecting the build in front of it instead of an artifact that may never have existed. πŸ”

And because a fixture that quietly does the right thing is how a contradiction like this hides for a whole release cycle, the test builder now honors the bundle formats it was actually handed. Ask it for dmg and you get a dmg β€” nothing more. The bug can't come back wearing a green test suite. βœ…

πŸ“¦ Getting it

bun update @kvnwolf/dobby

No config changes, no migration, nothing to re-declare. If your project has a release.type: "homebrew-cask" target, this is the version where bunx dobby release gets past the pack gate. Everyone else is unaffected β€” the npm release path never touched this code.

v0.12.0

Choose a tag to compare

@kvnwolf kvnwolf released this 11 Aug 06:43

🧭 One recipe, one build run, one trustworthy review boundary

Dobby 0.12.0 replaces execution-time guesswork with a fixed, inspectable workflow recipe. Every plan now enters one native Claude Workflow with the same agent roles, limits, and verification postureβ€”so runs are easier to understand, compare, and improve from evidence instead of configuration theory.

πŸ§ͺ A baseline you can actually measure

The new baseline-v1 recipe is exposed by bunx dobby env --json with a deterministic fingerprint. It pins the five worker roles to their intended model and effort, caps outer attempts and concurrency at two, and requires mechanical-first verification.

That contract is checked before any worker starts. If the recipe drifts between the plugin, agent definitions, and CLI, execution refuses early rather than silently running a different experiment. Dobby also records the evidence it can observeβ€”attempts, retries, outcomes, role calls, and verification sourcesβ€”while leaving unavailable provider and cost data honestly marked as unknown.

πŸ—οΈ A leaner build loop

/dobby:execute now runs the whole approved plan through a single native Workflow. Tasks move through optional spec-only test authoring, implementation, and independent verification; a failed verification gets at most one bounded fix-and-retry when another verifier attempt remains.

The duplicate per-task reviewer loop is gone. That saves context and calls without weakening the boundary: implementors still cannot verify their own work, terminal fixes are forbidden, blocked dependents do not run, and a write-capable worker with no usable work log triggers an independent safety review before the task is marked needs-human.

πŸ”Ž Review the complete change, not fragments

Holistic review now lives where the full story exists: the external PR boundary. dobby pr watch requires review evidence for the current PR HEAD, including Greptile's Last reviewed commit marker when Greptile is the configured gate. A stale summary or an old green review no longer makes a newer push look merge-ready.

This gives each layer one job: the build loop proves task behavior, the PR reviewer judges the complete diff, and merge readiness fails closed when either proof is missing.

πŸš€ Upgrade

bun update @kvnwolf/dobby

Reload the plugin after updating so the revised worker definitions are active:

/reload-plugins

v0.11.0

Choose a tag to compare

@kvnwolf kvnwolf released this 05 Aug 20:23

🌊 One plan, one run, and a chat that finally talks back.

/dobby:execute used to slice your plan into one workflow launch per wave and go quiet until the very end. v0.11.0 replaces that with the build run: a single workflow that carries the whole approved plan β€” waves sequenced inside it, tasks inside each wave still fully parallel, every safety the waves encoded (area-disjointness, destructive tasks isolated in their own wave) preserved byte-for-byte.

🚦 Live progress, where you actually are

The run narrates itself as it goes β€” a terminal line the moment each task lands (T3 βœ“ verified β€” … (2 loops)), retry lines the moment a review or verify pushes back, and a summary at every wave close. Verbosity is adaptive: a healthy task stays terse, and the first failed review or verify flips that task into full milestone narration while its siblings stay quiet. The detail shows up exactly where the trouble is.

And you don't have to keep the /workflows widget open anymore: the coordinator now relays the run into the chat β€” one quiet watcher over the run's journal, waking only when something terminal happened, one batched message per wakeup with everything new. No filler, no per-event spam; the chat lines are what survives, the widget stays the live view.

⊘ Blocked, not burned

When a task exhausts its caps and lands needs-human, everything that depends on it β€” directly or transitively β€” is now skipped as blocked: zero agents spawned, the blocker named in its row, independent tasks in the same wave untouched. No more burning a whole loop's worth of agents building on a foundation that never landed. (Powered by build-plan now exposing each task's dependsOn β€” an additive payload change.)

♻️ A dead run resumes, it never rebuilds

If the run dies mid-plan β€” or comes back empty β€” the play is resumeFromRunId with the same script and args: every completed agent replays instantly from cache and the script re-derives the full, authoritative result. The journal read is a forensic fallback, not the recovery path.

πŸ§ͺ The script is no longer prose-only

The build-run script that lives inside references/build-workflow.md is now pinned by a committed harness (cli/src/build-workflow.test.ts, 38 tests) that extracts the fenced script from the markdown and executes it under stubbed runtime globals β€” wave sequencing, blocked skipping, the narration lines, the args guards (including duplicate task ids and a task literally named __proto__) all proven, mutation-checked, before any real agents are involved. All recorded in ADR-0021.

⬆️ Upgrading

bun update @kvnwolf/dobby

Nothing else to do: no new config keys, no new hooks. The plugin side rides your marketplace auto-update β€” running Claude Code sessions need a /reload-plugins to pick up the new execute. /dobby:dispatch and /dobby:address-review keep their exact contracts (single-task tasks[], done status) β€” no consumer change there either.

v0.10.0

Choose a tag to compare

@kvnwolf kvnwolf released this 05 Aug 19:10

πŸŽͺ v0.10.0 β€” the interview learns to juggle

One thing changed for the better, and you'll feel it in the first minute of your next /dobby:scope session: the interview stops dripping questions one per turn and starts asking in frontier rounds β€” every question that's answerable right now travels together, and only the ones that genuinely depend on an open answer wait their turn.

❓ Frontier rounds in /dobby:interview

The old interview was thorough but slow on purpose: one focused question per turn, always. That rule was right about focus and wrong about cadence β€” when four open questions don't depend on each other, four round-trips are three too many.

The interview now models your task as a design tree and asks the frontier β€” every open decision whose prerequisites are already settled β€” in rounds:

  • πŸͺŸ Popup rounds first: up to 4 independent questions in one AskUserQuestion call, each still fully self-contained (its own 1–3 lines of context inside the question β€” the popup covers the turn's text, so nothing load-bearing lives outside it), each with a recommended answer.
  • ✍️ Text rounds after: open-ended questions arrive as their own numbered ❓ **Q1**… / ➑️ recommended list, so you answer several in one typed message.
  • ⛓️ Dependent chains still arrive one at a time β€” a question whose premise hangs on an open answer belongs to a later round. The old one-question interview didn't die; it's the degenerate case of the new mechanism.
  • πŸ”¬ Fact-finding no longer stalls the conversation: when a claim needs checking against the code, a researcher is dispatched and only the questions downstream of that fact wait β€” the rest of the frontier ships now.
  • ♻️ And if an answer invalidates a sibling answered in the same round, the orphaned answer is discarded and the question re-asked with the corrected premise β€” no decision ever stands on a premise that moved under it.

The closing machinery you rely on β€” the litmus test, the infra-assumption gate, the glossary term offer, the STATE.md persistence β€” is untouched. frontier, round, and design tree joined the glossary, ADR 0020 records the why, and the mechanism is adapted from mattpocock/skills' productivity/grilling (the attribution now points at the skill's current home β€” it was renamed upstream from batch-grill-me).

🀝 /dobby:finish closes the deal β€” literally

The other hop that stopped hurting: finish no longer makes you detour to GitHub to press the merge button. When your PR is merge-ready and the tree is clean, the finish gate now offers "Merge & finish" β€” it re-verifies merge-readiness (dobby pr watch), merges squashed, re-runs the preflight, and rolls straight into the teardown: dobby down, worktree + branch removed, main pulled. Your explicit selection is the merge decision β€” no path merges on its own, feedback-present still routes to /dobby:address-review, and /dobby:commit still never merges. The commit β†’ (merge on GitHub) β†’ finish lifecycle is now just commit β†’ finish.

⬆️ Upgrading

Nothing is asked of you: no new config keys, no hook changes, no rules that can go red. Update the devDependency and enjoy the shorter conversations:

bun update @kvnwolf/dobby

The plugin side ships with your marketplace auto-update; the batched interview and the merging finish are live the next time the skills load.