v0.0.174
Released Nov 11, 2026
Release Notes
Reports say why a rule did not give a verdict (JSON schema 1.9), squirrel audit has one exit code precedence with the outcome in the JSON and LLM reports, --resume says why it started a new crawl, an expired login is called out, settings writes no longer lose each other, and five rules stop flagging hidden, presentational and symbol-only content.
Added
- Reports carry a typed
outcomefor a rule that did not give a verdict.skippedChecksandnotCheckedentries in the JSON report haveoutcome:coverage_gap,timed_outorerrored(a rule that threw is told apart from one that ran out of time), beside thereasonstring, which is unchanged. One table in@squirrelscan/core-contractsnow says what each coded skip reason means (classifyRuleOutcome, withnot_applicable,rananddisabledas the other outcomes), and the checks that read skip reasons (isRuleNotApplicableReason,isRuleWideGapSkipReason, the not-checked set) derive from it, so a new reason without an entry fails a test instead of being misclassified. The JSON schema version is 1.9. (#1661) queuedis a run status, and a run's phase is a typed list.AgentRunStatusin@squirrelscan/core-contractsnow includesqueued(a run waiting for a container slot, which starts by itself; not terminal), so a client no longer types it away, and the REST API docs and thelist_auditstool say queued runs are listed.RunPhase(crawling,external-links,cloud,rules,walk,complete) andRunNotice(memory_pressure,crawl_scope) are exported,RunSyncDelta.phaseisRunPhase | null, andnextVisiblePhasekeeps a notice from overwriting the phase shown. (#1665)
Fixed
- A crawl's status can no longer go backwards.
stop()after a crawl completed used to writepausedovercompleted, and a late worker could rewrite a URL the frontier had already finished or that a resume had re-queued. Crawl and frontier status writes now follow one transition table (documented under Crawl status in the storage docs), checked in the same statement as the write. A write the table does not allow is ignored: it never throws and never stops a crawl.--resume, re-crawls andsquirrel analyzemove through the same edges as before. (#1659) squirrel auditcomputes its exit code in one place, and the JSON and LLM reports carry it. The code came from 34 assignments that overwrote each other, and an explicit--publishthat failed returned early and skipped the blocked code and the--fail-ongate. Now one precedence table decides it (documented under Exit codes): a blocked site with a failed explicit--publishexits3instead of1. A failed automatic publish still exits0, the documented choice, and is now visible:--format jsonhasoutcome(exit_code,status,publishwithstatusandreason) and--format llman<outcome>element. Both reports are written after publishing, so they can include its result. (#1657)- An expired or rejected login says so. Both used to read as signed out with no message, so a run quietly lost its cloud features, and a login the server had revoked was sent again on every command. Now the credential has a state (
env,login,expired,rejectedornone): the commands that run the session check print one stderr line namingsquirrel auth loginwhen the stored login is expired or rejected, a 401 from the production API on an account route (/v1/auth/*) is remembered for 24 hours so later commands do not send the login again, andsquirrel auth status(and--json, ascredentialState) reports the state. A 401 from another route, or from a server set withSQUIRREL_API_SERVER, is not remembered, and a login with an unreadable expiry reads as expired. (#1666) - Settings writes no longer lose each other, and one update state drives the banner, the prompt and doctor. Two commands saving different settings at the same moment (a background auto-update and a foreground
squirrel login, for example) both persist: each writer changes only its own keys under a lock file besidesettings.json, and a lock left by a dead process is taken over. Whether an update is pending, being attempted, failed (fallback), snoozed, dismissed or applied is now derived in one place, andsquirrel self doctorshows it next to the pending version. (#1660) squirrel audit --resumeandsquirrel crawl --resumesay why they started a new crawl. The resume decision is now one function shared by both commands. When a requested resume does not happen (no previous crawl, the previous crawl finished or failed, its data was reclaimed, the include, exclude or URL settings changed,--refreshwas passed, or another live process owns the crawl) the reason is printed on stderr, and--format jsoncarriesmeta.resumewithrequested,resumed, and thereasoncode andmessage. The old "previous crawl completed" message never printed, because a finished audit iscrawledoranalyzed, notcompleted. (#1658)images/offscreen-lazynames every image it reports and says how below the fold was decided. An<img>with an emptysrc(filled in later by script) no longer produces an item with an empty id: it is named by itsdata-srcfile, then itssrcsetordata-srcsetfile, then its selector path with a short snippet of its tag. The finding's details give theabove_fold_countvalue and state that no viewport is measured in an HTTP-only audit. (#1644)a11y/label-content-name-mismatchignores symbol-only visible text and controls that are not rendered.<button aria-label="Schließen">✕</button>passes: visible text with no letter or digit (✕,×,☰,›, emoji) is an icon, not a label a user would speak. A control under ahiddenordisplay:noneancestor or in a closed<dialog>is skipped, and one a style may hide but squirrel cannot settle is reported as unconfirmed. Real visible text with an unrelatedaria-labelstill fails. (#1645)a11y/aria-required-parentno longer flags the items of a progressbar list. A native<li>,<option>,<tr>,<td>,<th>or table section inside an ancestor whose children are presentational (an explicitroleof progressbar, img, button, checkbox, slider, tab and the others WAI-ARIA lists, or a native<progress>or<meter>) is not in the accessibility tree, so<ol role="progressbar"><li>no longer reports four listitems missing a list. A focusable element (tabindex,contenteditable) is still checked, and a genuine orphan<li>still fails. (#1640)a11y/table-duplicate-namecounts only tables in the accessibility tree. A table inside a closed<dialog>, or under ahidden(nothidden="until-found"),inert,aria-hidden="true"ordisplay:noneancestor, is no longer compared with the exposed tables, so two "Size" tables in separate closed dialogs are not a duplicate. Excluded tables are counted ashiddenTableson the check's details, and a table a style may hide but squirrel cannot settle is reported ontable-duplicate-name-unconfirmedinstead of as a duplicate. Two exposed tables with the same name still warn. (#1637)
Update to this version
squirrelscan will auto-update, or run this command to update now:
$