<?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: dormitivegit</title>
    <description>The latest articles on DEV Community by dormitivegit (@dormitivegit).</description>
    <link>https://dev.to/dormitivegit</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%2F4075575%2Fc99b2310-da10-4de6-86dd-27f9984c0a03.jpg</url>
      <title>DEV Community: dormitivegit</title>
      <link>https://dev.to/dormitivegit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dormitivegit"/>
    <language>en</language>
    <item>
      <title>Auto-compact can lose context. What can a verifiable handoff actually prove?</title>
      <dc:creator>dormitivegit</dc:creator>
      <pubDate>Fri, 13 Nov 2026 15:09:56 +0000</pubDate>
      <link>https://dev.to/dormitivegit/auto-compact-can-lose-context-what-can-a-verifiable-handoff-actually-prove-5b4b</link>
      <guid>https://dev.to/dormitivegit/auto-compact-can-lose-context-what-can-a-verifiable-handoff-actually-prove-5b4b</guid>
      <description>&lt;h2&gt;
  
  
  What actually gets lost
&lt;/h2&gt;

&lt;p&gt;Claude Code compacts a long conversation into a summary when the context window fills. Some things survive by&lt;br&gt;
design: the docs list the project-root &lt;code&gt;CLAUDE.md&lt;/code&gt;, auto memory and invoked skills (up to a size cap) as&lt;br&gt;
re-injected after compaction. What does not survive intact is the conversation itself: &lt;em&gt;"The summary keeps your&lt;br&gt;
requests and key code snippets, but detailed instructions from early in the conversation may be lost."&lt;/em&gt; A new&lt;br&gt;
session goes further: it &lt;em&gt;"starts with a fresh context window, without the conversation history from previous&lt;br&gt;
sessions."&lt;/em&gt; (&lt;code&gt;/clear&lt;/code&gt; keeps the old session on disk; you can &lt;code&gt;/resume&lt;/code&gt; it, but the new context starts empty.)&lt;/p&gt;

&lt;p&gt;So the gap is task state that lives only in the conversation: the approach you already rejected, the two tests that&lt;br&gt;
still fail, the deploy nobody has authorized yet. Practitioners see it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One DEV author tracked 18 instructions in each of two real conversations and found, after three compactions,
&lt;em&gt;"I had lost 33% of the instructions I tracked in one conversation and 57% in the other"&lt;/em&gt; — measured as whether
each item was still written down, not whether it was followed (&lt;a href="https://dev.to/murooka_az_dev/claude-code-forgets-my-instructions-as-compactions-pile-up-one-added-prompt-stopped-it-0-lost-4bn1"&gt;source&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Agents redo failed work because &lt;em&gt;"commit messages describe the diff that shipped, not the dead ends"&lt;/em&gt;
(&lt;a href="https://dev.to/icegaming/why-ai-coding-agents-keep-redoing-each-others-work-markdown-memory-vs-real-memory-in-cursor-and-14lj"&gt;source&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  When a handoff is worth it
&lt;/h2&gt;

&lt;p&gt;Often it isn't. The AI Hero handoff guide puts it plainly: &lt;em&gt;"&lt;code&gt;/compact&lt;/code&gt; unless something is travelling."&lt;/em&gt;&lt;br&gt;
(&lt;a href="https://www.aihero.dev/skills-handoff" rel="noopener noreferrer"&gt;source&lt;/a&gt;). Same task, same session: compact. Persistent rules: &lt;code&gt;CLAUDE.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A handoff earns its cost when work moves to a fresh session, another agent or a colleague &lt;strong&gt;and&lt;/strong&gt; resuming wrong is&lt;br&gt;
expensive. Compare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Plain:       Migration mostly done. Fix remaining issues and continue.
Structured:  Rejected: approach A (evidence: …)   State: 2 failing tests — recheck before trusting
             First action: inspect the failures   Deploy: NOT authorized
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second one is what &lt;a href="https://github.com/dormitivegit/session-handoff" rel="noopener noreferrer"&gt;session-handoff&lt;/a&gt; (Apache-2.0) asks the&lt;br&gt;
writer to produce: state and where it is recorded, what was corrected and must not be repeated, the first authorized&lt;br&gt;
action, and commands that re-verify anything that may have changed.&lt;/p&gt;
&lt;h2&gt;
  
  
  Three layers of checking — and what each cannot prove
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;What it specifies or checks&lt;/th&gt;
&lt;th&gt;What it does not prove&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Handoff contract (&lt;code&gt;SKILL.md&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;what a handoff must contain&lt;/td&gt;
&lt;td&gt;that the content is true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lint (18 rules)&lt;/td&gt;
&lt;td&gt;form: unanchored claims, missing cold read by a context-free reader, a pasted reading that no longer matches the file&lt;/td&gt;
&lt;td&gt;correctness — only 9 of the 18 rules have a registered test that shows them catching the failure they target&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Readout (&lt;code&gt;handoff_readout.py&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;numbers computed from the session transcript, pasted verbatim, reproducible byte-for-byte&lt;/td&gt;
&lt;td&gt;that the detectors are valid, or that the next session acts correctly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Abbreviated readout output (the full block is eight lines with an empty lesson table, nine with registered lessons):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;READOUT_SNAPSHOT  transcript path · lines read · sha256 · last timestamp · readout-script and lesson-table hashes
READOUT_PATTERNS  per registered lesson: violations / opportunities (previous five windows)
READOUT_RECEIVE   was a read call on the handoff file detected in the three tool calls after it was referenced
READOUT_RECOMPUTE the exact command that reproduces this block
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The public lesson table ships &lt;strong&gt;empty&lt;/strong&gt;: snapshot, receive, cost and size work out of the box; per-lesson counts&lt;br&gt;
need you to register patterns (&lt;code&gt;references/readout.md&lt;/code&gt;). Reproducibility tells you what was measured, not whether&lt;br&gt;
the measurement is right.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four measurement failures an independent review found
&lt;/h2&gt;

&lt;p&gt;Before collecting any outcome data, a second model reviewed version 2.6.0 in two phases: it sealed its own findings&lt;br&gt;
(hashes in a file) before it was allowed to open the author's numbers, and that order was checked from its&lt;br&gt;
transcript timestamps. This is an internal review with a different model, not external replication. It returned&lt;br&gt;
&lt;em&gt;fix before retest&lt;/em&gt;; all four findings were reproduced locally before fixing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Wrong observation source.&lt;/strong&gt; The working-directory lesson read 0 in a session whose writer admitted moving the
working directory. The detector parsed command text and watched only some directories. It now counts the host's
&lt;code&gt;environment&lt;/code&gt; records whose new working directory is neither the start directory nor a temp directory. (The
per-record &lt;code&gt;cwd&lt;/code&gt; field was tried first and dropped: it flips between record types — one session showed 186
"changes".)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Threshold from the wrong population.&lt;/strong&gt; Two rollback lines would have fired 44% and 16% of the time with nothing
changed (bootstrap over baseline sessions). One became a direct count; the other was re-calibrated to 5% on the
reference population — in-sample; the out-of-sample rate is not yet known.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The measurement disturbed itself.&lt;/strong&gt; One way of pasting the block back into the file changed the size reading,
so recompute stopped matching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mixed denominators.&lt;/strong&gt; A summed rate added lessons with different denominators; one lesson was 59% of all
violations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Separately, two coders labelling 211 user messages for "requirement not met" agreed at κ = 0.27 (one coder labelling&lt;br&gt;
twice: about 0.78). That label is not stable enough to be a baseline, so it isn't one.&lt;/p&gt;

&lt;p&gt;These numbers come from the author's private session corpus, which is not published (raw transcripts contain paths&lt;br&gt;
and code); the repository ships the detectors, tests and synthetic fixtures, not that corpus.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is established, and what is not
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Publicly verifiable: the repository ships the readout implementation and regression fixtures, and CI passes on
Linux with Python 3.12 and 3.13; the readout gives byte-identical output when the transcript snapshot, handoff file, prior-window inputs, script
version and paths are held constant.&lt;/li&gt;
&lt;li&gt;From the author's private evaluation (corpus not public): the registered lesson detectors passed their validity
checks, and in 131 of 138 internal handoffs the heuristic found a read call on the handoff file among the first
three tool calls after its filename appeared in one of the receiving session's first three user messages. That does not establish that the
read succeeded, that the content was understood, or that the next action was correct.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not established:&lt;/strong&gt; that receiving agents preserve every instruction or make fewer repeated mistakes. That needs a
comparison — plain summary vs. a generic structured handoff vs. this skill — on the same frozen tasks, scored by
something the writer cannot edit. The next round measures per-lesson recurrence over ~30 new handoffs; the result
gets published either way.&lt;/li&gt;
&lt;li&gt;Known limits: the readout parses Claude Code transcripts only (their format is internal and can change); the
receive check only recognizes handoff files named &lt;code&gt;HANDOFF_*.md&lt;/code&gt;; the
recompute line does not shell-quote paths with spaces.&lt;/li&gt;
&lt;/ul&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mkdir -p &lt;span class="s2"&gt;"$HOME/.claude/skills"&lt;/span&gt;
git clone https://github.com/dormitivegit/session-handoff.git &lt;span class="s2"&gt;"$HOME/.claude/skills/session-handoff"&lt;/span&gt;

&lt;span class="nv"&gt;SKILL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"$HOME/.claude/skills/session-handoff"&lt;/span&gt;
&lt;span class="nv"&gt;HANDOFF&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/absolute/path/to/HANDOFF_billing-migration.md"&lt;/span&gt;              &lt;span class="c"&gt;# name it HANDOFF_*.md (see limits above)&lt;/span&gt;
&lt;span class="nv"&gt;SESSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"$HOME/.claude/projects/your-project/session-id.jsonl"&lt;/span&gt;  &lt;span class="c"&gt;# the transcript of the session that wrote it&lt;/span&gt;

python3 &lt;span class="s2"&gt;"$SKILL/scripts/lint_handoff.py"&lt;/span&gt; &lt;span class="s2"&gt;"$HANDOFF"&lt;/span&gt;
python3 &lt;span class="s2"&gt;"$SKILL/scripts/handoff_readout.py"&lt;/span&gt; --session &lt;span class="s2"&gt;"$SESSION"&lt;/span&gt; --handoff &lt;span class="s2"&gt;"$HANDOFF"&lt;/span&gt;
python3 &lt;span class="s2"&gt;"$SKILL/tests/run_tests.py"&lt;/span&gt;                             &lt;span class="c"&gt;# the regression cases behind the fixes above&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most useful feedback is a sanitized failing case: a handoff where lint stayed clean but the next session still&lt;br&gt;
resumed wrong. Open an issue with it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>productivity</category>
      <category>testing</category>
    </item>
    <item>
      <title>Same pack, different CWD, different verdict</title>
      <dc:creator>dormitivegit</dc:creator>
      <pubDate>Wed, 26 Aug 2026 11:55:17 +0000</pubDate>
      <link>https://dev.to/dormitivegit/same-pack-different-cwd-different-verdict-534p</link>
      <guid>https://dev.to/dormitivegit/same-pack-different-cwd-different-verdict-534p</guid>
      <description>&lt;p&gt;Earlier in development, the same governance pack passed from one working directory&lt;br&gt;
and failed from another. Same pack bytes. Same referenced source bytes. Only the&lt;br&gt;
process working directory differed.&lt;/p&gt;

&lt;p&gt;A relative embedded source path was being resolved against the process working&lt;br&gt;
directory. A value the tool never declared as an input was changing the verdict.&lt;/p&gt;

&lt;p&gt;The defect became visible when the same check was run from a different working&lt;br&gt;
directory.&lt;/p&gt;
&lt;h2&gt;
  
  
  What the fix actually changed
&lt;/h2&gt;

&lt;p&gt;Relative embedded source paths now resolve against the pack's own directory instead&lt;br&gt;
of the process working directory. Absolute references stay absolute.&lt;/p&gt;

&lt;p&gt;For file-backed packs, relative references resolve from the pack's directory. With&lt;br&gt;
stdin there is no pack directory, so relative references are rejected rather than&lt;br&gt;
silently inheriting the process working directory. I required that stdin reject&lt;br&gt;
relative references instead of inventing a base directory.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why ordinary tests did not catch it
&lt;/h2&gt;

&lt;p&gt;A passing behavior test does not by itself prove that the files or evidence source I&lt;br&gt;
intended to check stayed bound.&lt;/p&gt;

&lt;p&gt;In an AI-assisted workflow, changes can arrive faster than I can re-check every&lt;br&gt;
assumption behind them. I wanted a small mechanical check for the things I had&lt;br&gt;
already decided mattered, while keeping the acceptance decision with me.&lt;/p&gt;

&lt;p&gt;This CLI adds a narrower deterministic check after ordinary tests. Its result is&lt;br&gt;
evidence for a reviewer or a CI system; it is not release authorization.&lt;/p&gt;
&lt;h2&gt;
  
  
  The smallest thing you can run
&lt;/h2&gt;

&lt;p&gt;I use the corpus command below because it is the shortest way to show the mechanism.&lt;br&gt;
It is not a reproduction of the path-resolution bug above.&lt;/p&gt;

&lt;p&gt;All commands below were replayed against &lt;code&gt;software-evidence-controls==0.4.0rc1&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;.&lt;/span&gt; .venv/bin/activate
python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install &lt;/span&gt;software-evidence-controls&lt;span class="o"&gt;==&lt;/span&gt;0.4.0rc1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a throwaway source directory, freeze it, change one byte, and verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;demo_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;source_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$demo_dir&lt;/span&gt;&lt;span class="s2"&gt;/source"&lt;/span&gt;
&lt;span class="nv"&gt;manifest&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$demo_dir&lt;/span&gt;&lt;span class="s2"&gt;/accepted-manifest.jsonl"&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$source_dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s1"&gt;'value = 1'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$source_dir&lt;/span&gt;&lt;span class="s2"&gt;/example.py"&lt;/span&gt;

software-evidence-controls corpus freeze &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$source_dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--manifest&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$manifest&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; json

&lt;span class="nv"&gt;manifest_sha256&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;python - &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$manifest&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import hashlib, pathlib, sys
print(hashlib.sha256(pathlib.Path(sys.argv[1]).read_bytes()).hexdigest())
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\n'&lt;/span&gt; &lt;span class="s1"&gt;'value = 2'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$source_dir&lt;/span&gt;&lt;span class="s2"&gt;/example.py"&lt;/span&gt;

software-evidence-controls corpus verify &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$manifest&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--accepted-manifest-sha256&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$manifest_sha256&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--expected-root&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$source_dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--format&lt;/span&gt; json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Abridged from the actual JSON output:&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;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HOLD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"findings"&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;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CI03_SOURCE_CHANGED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ERROR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"filesystem source bytes changed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example.py"&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;"exit_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"counts"&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;"match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"missing"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"changed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&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_changed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"self_ingested"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&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;This means the recorded source changed. It does not mean "reject the change." The&lt;br&gt;
finding is an input to CI or review; acceptance remains a human decision.&lt;/p&gt;

&lt;p&gt;The fastest way to evaluate this is to run it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the tool covers
&lt;/h2&gt;

&lt;p&gt;The CLI has several surfaces; these are the two used in this article.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;corpus freeze&lt;/code&gt; / &lt;code&gt;corpus verify&lt;/code&gt; records the bytes of a set of files you explicitly&lt;br&gt;
select, then verifies that those recorded sources have not changed. It does not prove&lt;br&gt;
that the scope you selected was complete.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;check&lt;/code&gt; validates a pack against explicit rules and returns a machine-readable&lt;br&gt;
PASS / FAIL / HOLD result with structured findings.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;--format json&lt;/code&gt;, these commands emit structured output meant to be consumed by&lt;br&gt;
CI or read by a reviewer rather than skimmed as a log.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this does not prove
&lt;/h2&gt;

&lt;p&gt;The published package declares no third-party Python runtime dependencies and&lt;br&gt;
requires Python 3.11 or newer. That is a statement about declared runtime&lt;br&gt;
dependencies, not about pip, the build backend, packaging tooling, or Python&lt;br&gt;
itself.&lt;/p&gt;

&lt;p&gt;I have not tested it on Windows. I have not exhaustively tested every CLI option&lt;br&gt;
combination, or long-running and repeated-operation behavior. The integrity check&lt;br&gt;
covers the sources you recorded, not the question of whether you recorded the right&lt;br&gt;
set. Other tools work on adjacent problems. I have no adoption data and make no&lt;br&gt;
claim about who else is using it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the name changed
&lt;/h2&gt;

&lt;p&gt;The project was previously published as &lt;code&gt;fable5-assurance-toolkit&lt;/code&gt;. In August 2026 I&lt;br&gt;
renamed the GitHub repository and published the Python distribution under&lt;br&gt;
&lt;code&gt;software-evidence-controls&lt;/code&gt;. Existing GitHub URLs redirect; the old PyPI prereleases&lt;br&gt;
remain as historical artifacts.&lt;/p&gt;

&lt;p&gt;Anthropic also uses "Fable 5" for a Claude model; this project is unrelated to&lt;br&gt;
Anthropic. The collision accelerated the rename, but the real problem was simpler:&lt;br&gt;
the old name spent its most visible word on an internal lineage label instead of&lt;br&gt;
saying what the tool does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who it is for
&lt;/h2&gt;

&lt;p&gt;It is for developers who can already say what must stay true after an AI-assisted&lt;br&gt;
change — which files, which manifest, which pack — and want that boundary checked&lt;br&gt;
mechanically.&lt;/p&gt;

&lt;p&gt;If you want a hosted service, a GUI, or an AI agent that decides what to accept for&lt;br&gt;
you, this is not that.&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/dormitivegit/software-evidence-controls" rel="noopener noreferrer"&gt;https://github.com/dormitivegit/software-evidence-controls&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;I used AI coding and analysis tools while building this. I decided the scope, what I&lt;br&gt;
was willing to claim publicly, and when public changes were authorized.&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>testing</category>
      <category>cli</category>
    </item>
    <item>
      <title>Probabilistic agents need deterministic acceptance boundaries</title>
      <dc:creator>dormitivegit</dc:creator>
      <pubDate>Thu, 13 Aug 2026 05:56:17 +0000</pubDate>
      <link>https://dev.to/dormitivegit/probabilistic-agents-need-deterministic-acceptance-boundaries-ae5</link>
      <guid>https://dev.to/dormitivegit/probabilistic-agents-need-deterministic-acceptance-boundaries-ae5</guid>
      <description>&lt;p&gt;I have been building with coding agents daily for a while now. They are good. The&lt;br&gt;
problem I keep running into is not that they write bad code — it is that they&lt;br&gt;
change things faster than I can produce evidence of what changed relative to what&lt;br&gt;
I had already accepted.&lt;/p&gt;

&lt;p&gt;This is a different problem from correctness, and I think it is being&lt;br&gt;
under-discussed.&lt;/p&gt;
&lt;h2&gt;
  
  
  Tests answer a different question
&lt;/h2&gt;

&lt;p&gt;When an agent hands back a change, the reflex is to run the test suite. If it&lt;br&gt;
passes, ship it.&lt;/p&gt;

&lt;p&gt;Tests answer: &lt;em&gt;does the code do what the tests assert?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;They do not answer: &lt;em&gt;what moved, relative to the state I reviewed and accepted?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Those come apart in ordinary situations. An agent refactors three files while&lt;br&gt;
fixing one. A generated migration rewrites a fixture you were treating as a&lt;br&gt;
frozen reference. A tool run mutates a config file nobody was watching. Every&lt;br&gt;
test still passes, because no test was ever written about the thing that moved.&lt;br&gt;
You find out later, or you do not find out.&lt;/p&gt;

&lt;p&gt;The gap widens as agents get more capable. A weak agent touches one function. A&lt;br&gt;
strong agent touches whatever it decides is in scope — and "whatever it decides"&lt;br&gt;
is precisely the part you cannot pin down in advance, because that&lt;br&gt;
non-determinism is where the value comes from.&lt;/p&gt;
&lt;h2&gt;
  
  
  The verification layer should be orthogonal to the agent
&lt;/h2&gt;

&lt;p&gt;The tempting fix is to make the agent verify itself: ask it to summarize its own&lt;br&gt;
changes, or run a second agent as reviewer.&lt;/p&gt;

&lt;p&gt;I do not think this works as an acceptance boundary, for a structural reason:&lt;br&gt;
both the change and the verification then come from the same probabilistic&lt;br&gt;
process. When they disagree you learn something. When they agree you have learned&lt;br&gt;
almost nothing, because agreement is exactly what a shared failure mode produces.&lt;/p&gt;

&lt;p&gt;What I want is a verification layer with the opposite properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic.&lt;/strong&gt; Same bounded input, same finding, every time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model-neutral.&lt;/strong&gt; Swapping the agent should not change what acceptance means.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline.&lt;/strong&gt; No network call in the verification path — a verification step
that can fail for network reasons is not a boundary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Machine-consumable.&lt;/strong&gt; A stable exit code and a structured finding, not prose
a human has to interpret.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that none of this makes the agent less useful. It stays as flexible and&lt;br&gt;
probabilistic as you like. The boundary is drawn at &lt;em&gt;acceptance&lt;/em&gt;, not at&lt;br&gt;
generation.&lt;/p&gt;
&lt;h2&gt;
  
  
  Freeze, change, verify
&lt;/h2&gt;

&lt;p&gt;The concrete mechanism I settled on is boring, which I take as a good sign.&lt;/p&gt;

&lt;p&gt;Freeze a bounded set of sources into a hash-addressed manifest. Let the agent do&lt;br&gt;
whatever it does. Verify against the manifest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;assurance corpus freeze ./src &lt;span class="nt"&gt;--manifest&lt;/span&gt; baseline.jsonl
&lt;span class="go"&gt;result=PASS
exit_code=0
manifest=baseline.jsonl
write_disposition=CREATED
source_record_count=1

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;... agent runs, edits a file ...
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;assurance corpus verify baseline.jsonl
&lt;span class="go"&gt;result=HOLD
exit_code=4
counts={"changed": 1, "match": 0, "missing": 0, "self_ingested": 0, "type_changed": 0}
FINDING {"code":"CI03_SOURCE_CHANGED","severity":"ERROR","message":"filesystem
source bytes changed","path":".../src/greeting.py","location":"source_record", ...}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Real output, with a few header lines — module id, rule-set version, profile —&lt;br&gt;
elided for width, and the absolute path shortened.)&lt;/p&gt;

&lt;p&gt;The two things that matter here are the ones that look least interesting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The exit code is part of the contract.&lt;/strong&gt; &lt;code&gt;4&lt;/code&gt; means an integrity finding&lt;br&gt;
specifically, not "something went wrong." A CI job can branch on it without&lt;br&gt;
parsing anything. Once exit codes are contractual they have to be versioned and&lt;br&gt;
tested like any other public interface, which is a constraint worth accepting&lt;br&gt;
early rather than discovering later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Bounded" is doing real work.&lt;/strong&gt; The manifest covers explicitly supplied roots.&lt;br&gt;
Not the whole machine, not an implicit working directory. An unbounded integrity&lt;br&gt;
check is one that eventually gets disabled because it is too noisy, and a&lt;br&gt;
disabled check is worse than no check because you still believe it is running.&lt;/p&gt;

&lt;p&gt;A fair objection at this point: for a clean Git repository, &lt;code&gt;git diff&lt;/code&gt; covers a&lt;br&gt;
substantial part of the ordinary file-change case. It genuinely does. The cases&lt;br&gt;
where it differs are when the bounded evidence set is not identical to the&lt;br&gt;
repository — several explicit roots at once, deliberately untracked files,&lt;br&gt;
members inside ZIP archives (read and hashed individually), symlink identity&lt;br&gt;
(the link target path itself is recorded and hashed, which is a different fact&lt;br&gt;
from the target file's contents), and the machine-consumable exit semantics&lt;br&gt;
above. If your evidence set is exactly "tracked files in one repo," use&lt;br&gt;
&lt;code&gt;git diff&lt;/code&gt;. It is right there and it is excellent.&lt;/p&gt;
&lt;h2&gt;
  
  
  Authorization must be out-of-band — a lesson I learned by getting it wrong
&lt;/h2&gt;

&lt;p&gt;This is the part I would most like to pass on, because I got it wrong in public&lt;br&gt;
and the failure mode generalizes well beyond my own project.&lt;/p&gt;

&lt;p&gt;Suppose your verification layer checks that a mutation was authorized: the record&lt;br&gt;
claims a decision authorized it, and the tool confirms the decision exists,&lt;br&gt;
covers the same object, and was made by the right authority.&lt;/p&gt;

&lt;p&gt;The question is: &lt;strong&gt;where does "the right authority" come from?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My first public implementation had the authority identity hard-coded as a literal&lt;br&gt;
string in the validation logic. It worked perfectly — for exactly one person.&lt;br&gt;
Every other user constructing a fully well-formed record got a HOLD, because&lt;br&gt;
their authority identity was not the one baked into the source. Three of the&lt;br&gt;
tool's modules were structurally unusable by anyone but me.&lt;/p&gt;

&lt;p&gt;I did not notice, and my test suite could not have told me, because all my&lt;br&gt;
fixtures used the same identity as the code. It surfaced during an independent&lt;br&gt;
adversarial review of the repository, and it surfaced only because the reviewer&lt;br&gt;
built two byte-identical inputs differing in exactly one field and observed that&lt;br&gt;
one passed and one did not. Reading the source had not found it; a green suite&lt;br&gt;
had not found it. &lt;strong&gt;A single-tenant constant hiding in validation logic is&lt;br&gt;
invisible from inside your own tests, because your fixtures share the constant.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The obvious repair is to let the input document declare its own authority. This&lt;br&gt;
is worse. If the record under verification names the authority that will be&lt;br&gt;
accepted, then a record can authorize itself:&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;"authority_identity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WHOEVER_I_SAY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decisions"&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;"decider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WHOEVER_I_SAY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AUTHORIZED"&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 validator dutifully confirms the two agree, and the check has become&lt;br&gt;
decorative. This is the same shape as a certificate that vouches for its own&lt;br&gt;
issuer.&lt;/p&gt;

&lt;p&gt;The repair that actually holds is to take the expected authority &lt;strong&gt;out of band&lt;/strong&gt; —&lt;br&gt;
supplied by the caller, at the call site, never readable from the artifact being&lt;br&gt;
checked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;assurance check pack.json &lt;span class="nt"&gt;--authority-id&lt;/span&gt; PROJECT_AUTHORITY   → PASS, &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;assurance check pack.json &lt;span class="nt"&gt;--authority-id&lt;/span&gt; SOMEONE_ELSE        → HOLD, &lt;span class="nb"&gt;exit &lt;/span&gt;3
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;assurance check pack.json                                    → HOLD, &lt;span class="nb"&gt;exit &lt;/span&gt;3
&lt;span class="go"&gt;                                                                  (fail-closed)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and, importantly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pack declares its own authority_identity, no &lt;span class="nt"&gt;--authority-id&lt;/span&gt; given
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;assurance check self-declaring-pack.json                     → HOLD, &lt;span class="nb"&gt;exit &lt;/span&gt;3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last two lines are the ones worth arguing about. Missing expected authority&lt;br&gt;
is a HOLD, not a pass-through — an authorization check with no expected authority&lt;br&gt;
has nothing to check against, and defaulting to permissive is how these things&lt;br&gt;
quietly stop working. And a self-declared authority never overrides the&lt;br&gt;
out-of-band value, even when it happens to agree with it.&lt;/p&gt;

&lt;p&gt;The transferable lesson is narrow and worth stating plainly: &lt;strong&gt;trust anchors do&lt;br&gt;
not belong inside the artifact being verified.&lt;/strong&gt; The second-order version is the&lt;br&gt;
one that nearly caught me — the naive fix for a coupling problem introduced a&lt;br&gt;
self-authorization hole, and it looked like a clean generalization while doing&lt;br&gt;
it.&lt;/p&gt;
&lt;h2&gt;
  
  
  The layer should refuse to make the decision
&lt;/h2&gt;

&lt;p&gt;The last design constraint is the one people push back on most, so I will state&lt;br&gt;
it plainly: this kind of tool should not decide whether to accept a change.&lt;/p&gt;

&lt;p&gt;Concretely, in mine, risk classification returns a tier &lt;em&gt;and&lt;/em&gt; an explicit field&lt;br&gt;
saying the classification is not an authorization. Handoff validation reports&lt;br&gt;
structural observations &lt;em&gt;and&lt;/em&gt; explicitly reports that receiver readiness was not&lt;br&gt;
machine-determined. Those fields are not decoration; they exist so that no&lt;br&gt;
downstream automation can quietly read a PASS as a go-ahead.&lt;/p&gt;

&lt;p&gt;The reason is not modesty about what software can do. It is that the moment a&lt;br&gt;
deterministic checker is treated as an approval authority, people start shaping&lt;br&gt;
inputs to satisfy it, and you have rebuilt the thing you were trying to avoid —&lt;br&gt;
a probabilistic process optimizing against a proxy. Keeping the tool&lt;br&gt;
descriptive, and keeping acceptance with a person, is what preserves the&lt;br&gt;
boundary's meaning.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where this leaves things
&lt;/h2&gt;

&lt;p&gt;I do not think "assurance for AI-assisted engineering" is a solved problem, or&lt;br&gt;
that a manifest checker is the whole answer. What I am fairly confident about is&lt;br&gt;
the shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;probabilistic generation  →  deterministic verification  →  human acceptance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with each stage refusing to do the next one's job. Agents stay flexible.&lt;br&gt;
Verification stays reproducible and inspectable. Acceptance stays with someone&lt;br&gt;
accountable.&lt;/p&gt;

&lt;p&gt;It is explicitly &lt;strong&gt;not&lt;/strong&gt; a replacement for Git, for tests, for CI, or for human&lt;br&gt;
review. It sits beside all four.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://github.com/dormitivegit/fable5-assurance-toolkit" rel="noopener noreferrer"&gt;FABLE5&lt;/a&gt; as one&lt;br&gt;
implementation of this shape — a local CLI, Python 3.11+ standard library only,&lt;br&gt;
no network calls, no daemon, no model invocation, Apache-2.0. It is early: a&lt;br&gt;
0.3.0 prerelease with 276 tests and CI across Python 3.11–3.14, maintained by&lt;br&gt;
one person. There is a self-contained runnable example that walks the whole&lt;br&gt;
freeze → change → detect → re-freeze cycle in a disposable temp directory in&lt;br&gt;
about two seconds.&lt;/p&gt;

&lt;p&gt;I would rather have the architecture argued with than the tool adopted. If you&lt;br&gt;
think the acceptance boundary belongs somewhere else, or that this is a problem&lt;br&gt;
existing CI already handles, I would genuinely like to hear it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>testing</category>
      <category>aiagents</category>
    </item>
  </channel>
</rss>
