sara-tasks 2.7.3

Sara — folder-aware task manager
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Sara — how it lives up to Impatient Programming</title>
<meta name="description" content="How sara's Contract, guardrails, mutation testing and auto-merge work together, mapped to the rules of Impatient Programming.">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><circle cx='16' cy='16' r='11' fill='%23CCFF4D'/></svg>">
<style>
:root{
  --bg:#060607;--ink:#F6F6F8;--mute:rgba(246,246,248,.56);--dim:rgba(246,246,248,.34);
  --line:rgba(255,255,255,.08);--glass:rgba(255,255,255,.026);
  --lime:#CCFF4D;--violet:#8B6CFF;--cyan:#4DE3FF;--amber:#FFC24D;--rose:#FF6B8A;
  --sans:-apple-system,BlinkMacSystemFont,"SF Pro Display","Segoe UI",Inter,system-ui,sans-serif;
  --mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,"Cascadia Mono",monospace;
}
*{box-sizing:border-box;margin:0;padding:0;}
body{background:var(--bg);color:var(--ink);font-family:var(--sans);line-height:1.55;-webkit-font-smoothing:antialiased;}
main{max-width:920px;margin:0 auto;padding:72px 24px 96px;}
a{color:var(--cyan);text-decoration:none;}
a:hover{text-decoration:underline;}
.back{font-family:var(--mono);font-size:13px;color:var(--mute);}
h1{font-size:clamp(40px,7vw,64px);letter-spacing:-.03em;line-height:1.05;margin:28px 0 12px;}
h1 em{font-style:normal;color:var(--lime);}
.lede{color:var(--mute);max-width:640px;}
h2{font-size:24px;letter-spacing:-.01em;margin:64px 0 8px;}
h2+p{color:var(--mute);margin-bottom:24px;max-width:640px;}
code,.chip,.rule{font-family:var(--mono);font-size:12px;}
code{color:var(--ink);background:var(--glass);border:1px solid var(--line);border-radius:5px;padding:1px 5px;}

.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;margin:40px 0 0;}
.stat{border:1px solid var(--line);background:var(--glass);border-radius:16px;padding:20px;}
.stat b{display:block;font-size:34px;letter-spacing:-.02em;}
.stat span{color:var(--dim);font-size:12px;font-family:var(--mono);}

.legend{display:flex;gap:16px;flex-wrap:wrap;margin:8px 0 28px;font-size:13px;color:var(--mute);}
.dot{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:6px;vertical-align:1px;}
.live{background:var(--lime);} .part{background:var(--amber);} .miss{background:var(--rose);}

/* The pipeline */
.flow{position:relative;padding-left:44px;}
.flow::before{content:"";position:absolute;left:15px;top:8px;bottom:8px;width:2px;
  background:linear-gradient(var(--violet),var(--cyan) 50%,var(--lime));opacity:.5;}
.phase{font-family:var(--mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--dim);margin:28px 0 10px;}
.phase:first-child{margin-top:0;}
.step{position:relative;border:1px solid var(--line);background:var(--glass);border-radius:16px;padding:18px 20px;margin-bottom:12px;
  animation:rise .5s ease both;}
@keyframes rise{from{opacity:0;transform:translateY(10px);}}
@media (prefers-reduced-motion:reduce){.step{animation:none;}}
.step::before{content:attr(data-n);position:absolute;left:-44px;top:16px;width:32px;height:32px;border-radius:50%;
  background:var(--bg);border:2px solid var(--violet);display:grid;place-items:center;font:600 13px var(--mono);color:var(--ink);}
.step.s-live::before{border-color:var(--lime);}
.step.s-part::before{border-color:var(--amber);}
.step.s-miss::before{border-color:var(--rose);}
.step h3{font-size:17px;display:flex;gap:10px;align-items:center;flex-wrap:wrap;}
.step p{color:var(--mute);font-size:14.5px;margin-top:6px;}
.step .where{margin-top:10px;display:flex;gap:6px;flex-wrap:wrap;}
.chip{display:inline-block;border:1px solid var(--line);border-radius:6px;padding:2px 7px;color:var(--mute);}
.rule{border-radius:99px;padding:2px 9px;background:rgba(139,108,255,.14);color:#BBA9FF;font-size:11px;}
.gap{margin-top:10px;font-size:13.5px;color:var(--amber);}
.s-miss .gap{color:var(--rose);}

/* The bug loop */
.loop{display:grid;grid-template-columns:repeat(5,1fr);gap:10px;align-items:stretch;}
.loop div{position:relative;border:1px solid var(--line);background:var(--glass);border-radius:14px;padding:14px;font-size:13.5px;color:var(--mute);}
.loop div b{display:block;color:var(--ink);font-size:14px;margin-bottom:4px;}
.loop div:not(:last-child)::after{content:"→";position:absolute;right:-12px;top:50%;transform:translateY(-50%);color:var(--cyan);font-family:var(--mono);}
.loop .red b{color:var(--rose);} .loop .green b{color:var(--lime);}
.example{margin-top:14px;border-left:2px solid var(--cyan);padding:4px 0 4px 14px;color:var(--mute);font-size:14px;}
@media (max-width:720px){.loop{grid-template-columns:1fr}.loop div:not(:last-child)::after{content:"↓";right:50%;top:auto;bottom:-16px;transform:none;}}

/* Scorecard */
table{width:100%;border-collapse:collapse;font-size:14px;}
th,td{text-align:left;padding:10px 12px;border-bottom:1px solid var(--line);vertical-align:top;}
th{color:var(--dim);font-weight:500;font-family:var(--mono);font-size:12px;text-transform:uppercase;letter-spacing:.06em;}
td:first-child{white-space:nowrap;font-family:var(--mono);font-size:12px;color:var(--dim);}
td:nth-child(3){white-space:nowrap;}
footer{margin-top:64px;color:var(--dim);font-size:13px;}
</style>
</head>
<body>
<main>
  <a class="back" href="./">← sara</a>
  <h1>Sara, the <em>IMP</em> way</h1>
  <p class="lede">How a change to sara travels from an agreed obligation to a release, and which rule of
    <a href="https://impatientprogramming.org/">Impatient Programming</a> each stage keeps.
    Typing is cheap; keeping control of the code is not, so every stage is a machine check, not a promise.</p>

  <div class="stats">
    <div class="stat"><b id="obligations">–</b><span>Contract obligations</span></div>
    <div class="stat"><b id="percent">–</b><span id="covered">of commands and tools covered</span></div>
    <div class="stat"><b>18</b><span>checks per PR</span></div>
    <div class="stat"><b>1 / 128</b><span>kill-matrix shard a night</span></div>
  </div>

  <h2>The flow of one change</h2>
  <p>One pull request breathes in and out above the Contract. Each numbered stage below runs on its own; a person only looks where the blast radius calls for it.</p>
  <div class="legend">
    <span><i class="dot live"></i>in place</span>
    <span><i class="dot part"></i>in place, with a gap</span>
    <span><i class="dot miss"></i>missing</span>
  </div>

  <div class="flow">
    <div class="phase">Agree</div>

    <div class="step s-part" data-n="1">
      <h3>Write the obligation <span class="rule">Rule 13</span><span class="rule">Rule 20</span></h3>
      <p>Before the code, what sara must do goes into the Contract as one numbered sentence a test can prove,
        such as <em>ids/A6: a blank id matches nothing</em>. Obligations are never renumbered. A <code>covers</code> block names the
        code they are about and a <code>surface</code> block the commands and MCP tools their tests drive.</p>
      <div class="where"><span class="chip">contract/&lt;primitive&gt;/README.md</span></div>
      <p class="gap">Only the <code>ids</code> primitive is written so far. 52 contract tests in 15 files still need obligations (<a href="https://github.com/Abarbesgaard/Sara/issues/316">#316</a>).</p>
    </div>

    <div class="step s-live" data-n="2">
      <h3>Make it executable <span class="rule">Rule 20</span></h3>
      <p>Each contract test names the obligations it proves with <code>#[impish::contract("ids/A6")]</code>, which
        fails to compile when the tag points nowhere. <a href="https://github.com/Abarbesgaard/impish">impish</a>'s
        <code>cargo impish check</code> fails the build when an obligation has no test, a <code>covers</code> or
        <code>surface</code> line names something sara doesn't have, or the published coverage is stale.</p>
      <div class="where"><span class="chip">tests/contract/&lt;primitive&gt;.rs</span><span class="chip">cargo impish check</span></div>
    </div>

    <div class="phase">Breathe in, breathe out</div>

    <div class="step s-live" data-n="3">
      <h3>Build it, then fold it back <span class="rule">Rule 12</span><span class="rule">Rule 16</span><span class="rule">Rule 21</span></h3>
      <p>The agent sprawls until the feature shows its shape, then the same PR folds what was learned back into
        what exists. Parsers are checked with generated input (proptest): add tokens, dates, plan import, and
        256 schema-shaped calls to every MCP tool. Their shrunk failures are kept as regression files.</p>
      <div class="where"><span class="chip">tests/unit/…</span><span class="chip">tests/contract/mcp_args.rs</span><span class="chip">tests/proptest-regressions/</span></div>
    </div>

    <div class="phase">Guardrails fire on every change</div>

    <div class="step s-live" data-n="4">
      <h3>Standards, enforced by machines <span class="rule">Rule 19</span><span class="rule">Rule 24</span></h3>
      <p>Formatting, Clippy, unused dependencies, workflow security (zizmor) and dependency policy (cargo-deny) run first.
        Architecture rules (archunit) keep the domain layers pointing inwards and the command slices apart.
        New similar code fails the PR unless it is declared in <code>scripts/similar-code.allow</code>, so duplication gets folded.</p>
      <div class="where"><span class="chip">ci.yml (Lint, Policy)</span><span class="chip">tests/architecture.rs</span></div>
    </div>

    <div class="step s-part" data-n="5">
      <h3>Tests on every platform <span class="rule">Rule 23</span></h3>
      <p>The whole suite, unit and contract, runs on Linux, macOS and Windows; on Linux it runs again without telemetry.</p>
      <div class="where"><span class="chip">Test</span><span class="chip">Portability (os)</span></div>
      <p class="gap">Non-functional obligations aren't gated yet: performance (<a href="https://github.com/Abarbesgaard/Sara/issues/299">#299</a>) and Windows as a Contract obligation (<a href="https://github.com/Abarbesgaard/Sara/issues/298">#298</a>).</p>
    </div>

    <div class="step s-live" data-n="6">
      <h3>Mutants on the PR diff <span class="rule">Rule 26</span></h3>
      <p>cargo-mutants changes every line the PR touched (<code>+</code> becomes <code>*</code>, a <code>!</code> disappears, a function returns a default)
        and runs the tests against each mutant. A mutant no test catches fails the PR: new code must come with a test that notices when it breaks.</p>
      <div class="where"><span class="chip">Mutants</span><span class="chip">.cargo/mutants.toml</span></div>
    </div>

    <div class="phase">Merge and release</div>

    <div class="step s-live" data-n="7">
      <h3>Green merges itself <span class="rule">Rule 27</span></h3>
      <p>A single <code>CI</code> job needs every other job and fails if any of them failed or was cancelled. Main's ruleset requires
        only that check, so <code>gh pr merge --auto --squash</code> lands a PR the moment it is green, with no human in the loop.</p>
      <div class="where"><span class="chip">CI</span><span class="chip">ruleset: protection on main</span></div>
    </div>

    <div class="step s-live" data-n="8">
      <h3>Every green merge is a release <span class="rule">Rule 3</span></h3>
      <p>A PR that changes what ships (<code>src/</code>, <code>assets/</code>, <code>Cargo.toml</code>, <code>Cargo.lock</code>) fails until it
        bumps the version in <code>Cargo.toml</code>. Once CI is green on <code>main</code>, the same run tags the new version and ships it: binaries, the <code>.deb</code>, crates.io and the apt repository.</p>
      <div class="where"><span class="chip">ci.yml (Policy, Release)</span><span class="chip">release.yml</span></div>
      <p class="gap">Dependabot can't bump the version, so its updates ship with the next release.</p>
    </div>

    <div class="phase">Keep it honest</div>

    <div class="step s-part" data-n="9">
      <h3>Every test proves it's worth keeping <span class="rule">Rule 26</span></h3>
      <p>The kill matrix runs every test against every mutant, one of 128 shards a night, and records which tests catch what.
        A test that catches nothing another test doesn't already catch gets an issue: delete it, or close the issue to keep it.</p>
      <div class="where"><span class="chip">kill-matrix.yml</span><span class="chip">scripts/kill-matrix.sh</span><span class="chip">branch: kill-matrix-data</span></div>
      <p class="gap">It files issues but doesn't fail the build yet (<a href="https://github.com/Abarbesgaard/Sara/issues/296">#296</a>).</p>
    </div>

    <div class="step s-live" data-n="10">
      <h3>Coverage is published <span class="rule">Rule 29</span></h3>
      <p>Every CI run prints Contract coverage from <code>contract/coverage.json</code> in its job summary:
        obligations per primitive, and which commands and tools no obligation covers yet.</p>
      <div class="where"><span class="chip">contract/coverage.json</span><span class="chip">ci.yml (Test)</span></div>
    </div>
  </div>

  <h2>When a bug is found</h2>
  <p>A bug means the Contract was missing something (Rule 28). The obligation comes first and the fix comes last.
    The <a href="https://github.com/Abarbesgaard/Sara/issues/new?template=bug.yml">Bug form</a> asks for the missing obligation alongside the steps to reproduce.</p>
  <div class="loop">
    <div><b>1. Obligation</b>Write the promise the bug broke into the Contract.</div>
    <div class="red"><b>2. Red</b>Its test fails against the unchanged code.</div>
    <div><b>3. Kills the mutant</b>The test catches the mutant that matches the bug.</div>
    <div class="green"><b>4. Fix</b>Change the code until the Contract is met.</div>
    <div><b>5. Label</b><code>fix:</code> for a bug in a release, <code>refine:</code> for anything else (Rule 9).</div>
  </div>
  <p class="example">Example: generated MCP calls found that <code>sara done ""</code> completed a task, because a blank id matched every id.
    <em>ids/A6</em> went in with the test <code>a_blank_id_matches_nothing</code>, and only then did the lookups refuse a blank id.</p>

  <h2>Scorecard</h2>
  <p>The engineering rules, where sara stands on each, and what's left to do. The team rituals (the weekly showcase and the daily brief) aren't listed.</p>
  <table>
    <thead><tr><th>Rule</th><th>What IMP asks</th><th>Status</th><th>How sara does it</th></tr></thead>
    <tbody>
      <tr><td>1, 20</td><td>Success criteria written down first</td><td><i class="dot live"></i>in place</td><td>Every issue starts from a form that asks for obligations in the Contract's format; there are no blank issues</td></tr>
      <tr><td>13, 20</td><td>An executable Contract</td><td><i class="dot part"></i>partial</td><td>Checker and tagging are in place; one primitive written (<a href="https://github.com/Abarbesgaard/Sara/issues/316">#316</a>)</td></tr>
      <tr><td>29</td><td>Contract coverage published</td><td><i class="dot live"></i>in place</td><td>CI job summary</td></tr>
      <tr><td>19, 24</td><td>Machines enforce standards</td><td><i class="dot live"></i>in place</td><td>fmt, Clippy, unused dependencies, zizmor, cargo-deny, archunit</td></tr>
      <tr><td>26</td><td>Every test earns its place</td><td><i class="dot part"></i>partial</td><td>Mutants fail PRs; the nightly kill matrix files issues but doesn't fail the build (<a href="https://github.com/Abarbesgaard/Sara/issues/296">#296</a>)</td></tr>
      <tr><td>27</td><td>Green merges itself</td><td><i class="dot live"></i>in place</td><td>The <code>CI</code> gate is required; auto-merge is on</td></tr>
      <tr><td>3</td><td>Every green merge is a release</td><td><i class="dot live"></i>in place</td><td>The Version check makes every shipping PR bump</td></tr>
      <tr><td>28</td><td>A bug adds an obligation first</td><td><i class="dot live"></i>in place</td><td>Practised, e.g. <em>ids/A6</em></td></tr>
      <tr><td>9</td><td>PRs labelled honestly</td><td><i class="dot live"></i>in place</td><td><code>pr-title.yml</code>: <code>fix:</code> needs the <code>bug</code> label; <code>refine:</code> is labelled</td></tr>
      <tr><td>—</td><td>Non-functional obligations</td><td><i class="dot miss"></i>missing</td><td>Performance (<a href="https://github.com/Abarbesgaard/Sara/issues/299">#299</a>) and Windows (<a href="https://github.com/Abarbesgaard/Sara/issues/298">#298</a>)</td></tr>
    </tbody>
  </table>

  <footer>Rules quoted from <a href="https://impatientprogramming.org/primer.md">the IMP primer</a>. Live numbers come from
    <a href="https://github.com/Abarbesgaard/Sara/blob/main/contract/coverage.json">contract/coverage.json</a>.</footer>
</main>
<script>
fetch("coverage.json")
  .then(r => { if (!r.ok) throw new Error(r.status); return r.json(); })
  .then(c => {
    document.getElementById("obligations").textContent = c.obligations;
    document.getElementById("percent").textContent = c.surface.percent + "%";
    document.getElementById("covered").textContent = `of ${c.surface.total} commands and tools covered`;
  })
  .catch(() => {});
</script>
</body>
</html>