Imported from xkazm04/ai-registry (
skills/scan-sweep/SKILL.md). Install upstream withnpx skills add xkazm04/ai-registry --skill scan-sweep. Copyright stays with the author.
Context Sweep
A long-running sweep that owns a repository's quality loop. It walks the context map one area at a time, reads each area's code once, judges what it read through every relevant lens, and lands the safe fixes itself.
The expensive part of any scan is reading the code. Do it once per context, then judge. Depth beats breadth within a context; breadth comes from the loop returning to the next-least-covered area, not from skimming.
Invocation
/scan-sweep # the LOOP: stabilize, least-covered context first, until stopped
/scan-sweep --one <context> # exactly one context, then stop
/scan-sweep --coverage # the pick list; scan nothing
/scan-sweep --ideas-only # scan and propose; change no code
Default = the stabilize loop. No arguments means: pick the least lens-covered context, sweep it, land what is safe, record coverage, then pick the next one and say so - round after round until the operator stops the session or every context has been swept. Each round is self-contained: its commits are landed and its snapshot is written before the next context is chosen, so an interrupted loop loses nothing but the round in flight.
Announce each round's boundary out loud: โโ Round <n>: <context> (lens coverage <a>/<total>, last swept <age>) โโ. A loop whose rounds are not visible
reads as one runaway session.
Strategies - pick at most one
-
--stabilize(DEFAULT) - make what exists solid. Deep tier: bounty-hunter, parity-auditor, error-handler, observability-auditor, risk-assessor, code-optimizer, ux-reviewer, visual-craft, state-coverage, copy-auditor, accessibility-checker, test-strategist, security-auditor. Aim ~80% of the finding budget at defects, broken or unpolished UI states, and measured performance problems. Feature ideas and architecture proposals are recorded as findings only - never built under this strategy.Five of those thirteen were added because the eight before them kept missing whole classes (measured over two full rounds on one repo, 2026-09-04):
parity-auditorbecause one rule with two implementations, only one fixed, was five of the ten defects found and no other lens is looking for it;observability-auditorbecause a swallowed error and a missing audit row are invisible to every lens that reads for correctness; andvisual-craft,state-coverage,copy-auditorbecause this strategy's own blurb promises "broken or unpolished UI states" whileux-revieweralone owns flow, leaving how a surface LOOKS, which states it can enter, and what it SAYS unassigned - and copy that asserts a removed behaviour ships to a human who then acts on it. -
--develop- NEW capability. Deep tier: feature-scout, innovation-catalyst, ux-reviewer, onboarding-designer, integration-planner, business-strategist, growth-hacker, monetization-advisor. ~70% of the budget at forward-building items. Quality lenses still run as a light pass - a real defect is never ignored, but marginal cleanups are dropped."New" is not a reason to write
unmeasurable. A capability that lives inside what the context already declares it does is COVERAGE, and its figure is a behaviour test the sweep writes first: Before =0 of N cases pass, After =N of N, Method =gate. Sized S or M, it builds (ยง5). What stays human under this strategy is a direction - a capability the context's declared scope does not name - and anything L. Under v2.x every forward-building item wasunmeasurableon arrival and the strategy had no auto-build path at all;references/evidence.mdcarries the recipe per lens. -
--optimize- QUALITY of what exists, deeper than stabilize goes: adds tech-debt-tracker, dependency-auditor, devops-optimizer, documentation-auditor, mobile-specialist, observability-auditor. ~70% of the budget at hardening, debt and coverage.Every hardening claim has a count. Debt is duplicated sites; a dependency is an audit line, a version delta and a green gate; a pipeline is a duration or a step count; a doc is a stated rule checked against the code it describes; a mobile issue is a reproduced viewport. "Cleaner" or "more maintainable" with no figure is
not-better(pure churn), neverunmeasurable- theunmeasurabledrawer is where this strategy's whole output went under v2.x.
Name the strategy in the report header and record it in the snapshot's strategy
field.
1. Scope the round
--one <context>names the context. Otherwise pick it: read the context map and the sweep history, and choose, in this order - the first context in map order with NO snapshot at all; else the smallestlens_keysunion (fewest lenses ever applied); tie โ oldest latest snapshot.scripts/coverage.mjs --nextcomputes exactly this and is the cheapest way to ask.- State the choice and why in the round header ("never swept" / "lens coverage 4/22, oldest 2026-08-11"). Coverage rotation must be auditable.
- Stay inside the context's declared file paths for the whole round.
2. Load shared awareness - BEFORE reading code
- Backlog memory (overlay key
backlogDigest, default.personas/backlog-digest.json) - pending / accepted / rejected titles. Never re-propose anything on those lists, including rephrasings of rejected titles. A rejected title is a durable human "no". - Hard gates (
.claude/conventions.jsonwhen present) - a finding that violates a declared gate is a defect you are about to introduce, not a finding. - Sweep history (
.claude/scan-history/scan-sweep.jsonl) - prior snapshots, for the trend line and the lens-ordering rule. - The registry, when this repo consumes one - see ยง6. The registry is
KNOWLEDGE THAT FEEDS THE LENSES, not a lens of its own and not the sweep's
primary instrument: each lens reads the governing subject's techniques that
touch its concern and judges against them (ยง3). The one place where
"registry deviation" is itself the finding is the
registry-conformancelens, which runs like any other lens and is budgeted like any other lens.
3. Pick the lens package
--lenses k1,k2uses exactly those keys.- Otherwise the package is ALL lenses in
references/lenses.md, ordered: the active strategy's deep tier first, then lenses whoseMatchregex hits the context's name, description, keywords, stack or paths, then the rest as a lighter pass. Most of the tail will honestly report "nothing real", and that clean verdict IS coverage worth recording. - Within each tier, never-applied lenses (absent from prior
lens_keys) go first. The package's job is to close lens coverage, not re-walk it. - List the deep / matched / remaining keys in the round header.
- Lenses are the instrument; the registry is what sharpens them. A scan is
a set of prompts applied to the code -
error-handlerasks about error doors,code-optimizerabout render and query cost - and when the repo consumes a registry (ยง6), each lens FIRST reads the techniques in the governing subject that touch its concern, then judges the code against them and names the technique in the finding. A lens whose concern has no governing knowledge judges on the repo's own conventions and says so in the finding. Theregistry-conformancelens is the ONLY lens whose findings are the deviations themselves - the pure registryโbacklog transformation - and every other lens leaves those to it rather than restating them.
4. Survey, then judge
-
Read the context's files and collect evidence FIRST - form no verdicts while still reading.
-
Run any cheap deterministic check that applies (type-checker, linter, existing script) and reconcile. Deterministic findings belong to those tools - do not restate them as findings.
-
Walk the lens package sequentially, and give each tier room to report: deep tier โค3 findings, matched tier โค2, remaining tier โค1, each grounded in
file:line. Zero from one lens is a valid result - say "nothing real" and move on. Zero from a whole TIER is a claim about the codebase, and ยง4.9 is where you test it. -
FIND GENEROUSLY, BUILD CONSERVATIVELY. These are different budgets and confusing them is the failure this clause exists to prevent.
How much work a round does is decided by the ROUTING RULES in ยง5 - S builds, M clears a ratio, L never builds - and not by how many findings exist. So the finding budget is not a work budget: raising it raises the BACKLOG, which is the artefact the operator triages, and leaves the build volume where ยง5 put it. A sweep that finds five things in a 24-file context has not been disciplined; it has been incurious, and it has left the operator a backlog that under-describes their own repository.
Budget: 16 findings per context per round.
--depth Noverrides;--oneraises it to 24, because a named context is a deliberate deep dive. Lifetime cap per context: 40. Subtract what prior snapshots already reported, and never re-emit a finding already reported or present in the backlog digest.It was 12 while the stabilize deep tier was 8 lenses. The tier is 13 now, and this clause's own rule - the budget must absorb the package you ran - applies to the package this skill ships by default, not only to one an operator narrows by hand.
THE BUDGET MUST BE ABLE TO ABSORB THE PACKAGE YOU RAN. A 22-lens package against a 5-item budget is exhausted by lens three, and the other nineteen have nowhere to put anything - they become coverage RECORDING, the ledger reads 22/22, and the round reports a clean tail it never had room to hear. If you narrow the budget, narrow the package with it (
--lenses), or the coverage number is a lie you told yourself. Measured 2026-08-27: a 5-item budget over a full package yielded 0.098 findings per lens-pass against the same repository's 1.63 under a 6-lens package a fortnight earlier - 17ร less, from 3.6ร more lenses. -
Score both sides of every candidate. Reward = user-visible or developer-measurable gain (impact 1-10). Risk = chance of breaking working code, plus churn - lines rewritten per unit of gain (1-10). These two numbers drive every routing decision in ยง5, so guessing them is guessing the routing. Two hard rules learned from calibration:
- "Unused/dead" claims require proof. A finding that says dead/unused MUST cite its zero-consumer grep in the evidence. Verified dead-code removal is the best reward/risk class there is; guessed dead-code removal is the worst.
- Repo-declared incremental migrations (string extraction, token adoption
- whatever the repo calls fix-as-you-touch) are in scope for the nearest lens in files you already read, never as a bulk migration, and never where a deterministic gate already tracks them.
-
Hunt for pairs, not for defect shapes. On any codebase already swept a few times, pattern greps yield near zero. What still hits is two implementations of one rule that must agree, where only one was fixed - client vs server validation, a gate vs its debit, an abstraction vs its un-migrated call sites, a doc's stated rule vs the code. Grep for the shared symbol and diff its call sites. The signal to abandon a grep battery is the second clean result, not the fifth.
-
Interrogate every hand-maintained list. Coverage lists, allow-lists, enumerations of tables or routes - ask what enumerates the ground truth, and is the test derived from that or from the list? A test that reads the implementation's own list is coverage theater, and this class produces the highest-impact findings a sweep can find.
-
BEFORE you declare the round, check the yield. A full package over a context of ten files or more should produce roughly 10-16 findings. Fewer than 8 is a signal about YOUR PASS, not about the codebase - the usual cause is that you read the deep tier and let the tail report "nothing real" without ever pointing it at anything. Under 8, do one more pass before declaring: open the two largest files you only skimmed, and drive the three never-applied lenses at something specific rather than at the context in general.
A genuinely clean round is possible and must stay reportable - but it is a CLAIM, so state what you did to earn it: which files you read in full, which hypotheses you traced and why each failed. "Nothing found" and "nothing looked for" produce identical reports otherwise, and only one of them is a result. Measured 2026-08-27: eight of thirteen rounds returned two findings or fewer, none hit the budget, and re-running one of them under this clause found the yield had been the method's, not the repository's.
-
A clean lens is only credible once the tier around it has spoken. If an entire tier reports nothing, name the three things in it you actually checked. The tail's job is coverage AND a lighter hunt - it is not a list of keys to write into the ledger.
-
EVERY finding is written in the standard form - no exceptions, no prose dumps. The operator decides from the deck at a glance and a cheaper model (Sonnet, Haiku) executes from the text alone, so a finding that is vague where another is precise is a finding that will be mis-decided or mis-built. The
bodyof a finding is markdown with exactly these##sections, in this order, each present even when short:## Summary One or two sentences: what is wrong (or missing) and where. No reasoning. ## Description What the code does today, why that is a defect or a gap, and what "fixed" looks like. Name the technique/golden path it violates when one applies. `file:line` for every claim. ## Flow - the steps that reproduce or expose it, as bullets - user action -> code path -> observed result - for a proposal: the steps the fix takes, in build order ## Expected impact Who notices, what changes for them, and how it would be measured. One sentence on what could break. ## Evaluation Claim: quality | performance | resilience | user | other - <what the idea promises, one clause> Before: <the measurement today - a number, a count, a reproduced behaviour, or the sample you looked at> After: <the same measurement under the proposed change - from a gate run, a probe on a small sample, a harness, or three walked cases> Method: gate | probe | experiment | simulation - <what you actually did to get the two figures, and why the rung above was not reachable> Result: better | not-better | unmeasurable Gate: none | contract | policy-tighten | policy-loosen | irreversible | architecture | directionThe Evaluation is the routing step (ยง5), written down - and it is a MEASUREMENT, not an opinion. "Net positive" was the previous form, and it let a plausible story pass for evidence: measured on this operator's deck (2026-08-28), 44 of 64 human-gated ideas were
uncertainbecause nobody had checked the claim, and the executors then found premises false at the point of build. So: pick the benefit the idea actually promises, take the figure BEFORE (count the sites, time the path, reproduce the failure, read the sample), take the same figure AFTER by the highest rung of the ladder you can reach (ยง4.11), and write both down.bettermeans the After figure is better on the claimed dimension without a worse figure on another you can see.not-betteris a finding you are REJECTING with its numbers attached.unmeasurableis reserved for what genuinely has no figure - a matter of taste, a product bet - and may only be written AFTER the ladder was climbed and the card says which rung failed and why. It is the only result that goes to a human for the benefit question itself. Pure churn measures the same before and after and is thereforenot-better. -
Climb the evidence ladder before writing
unmeasurable. The rungs, top down:gate- a test or repo gate that goes red-then-green on the change, or a gate's exit code on both sides;probe- the change applied to a small sample and the same figure taken on both sides (three call sites, one timed path, one reproduced flow);experiment- the same inputs run twice through a harness that ships nothing (a script over a store copy, a replayed session, a dry run of a hook);simulation- three concrete cases from this tree or its history walked under A and B, with what would falsify the prediction. Take the highest rung reachable in the round and say in the Method line why the one above was not. When the only thing between a claim and its figure is an S-sized instrument - a test file, a counter, a timing wrapper, a fixture - build the instrument as its own S fix first, then measure. A sweep that writesunmeasurablewhere a twenty-line test would have decided it has skipped its own method.references/evidence.mdcarries the rungs, the reachable conditions and the per-strategy recipes.evidenceis SEPARATE frombodyand is the proof, as a code block or afile:linelist - the exact lines, the grep output, the count - never a restatement of the Description.titleis the Summary compressed to one line (โค 80 chars, imperative for a fix, noun phrase for a defect).The renderer (
TriageCardBody) splits on these headings and paints each as its own block; a body without them paints as one undifferentiated block and reads as the lower-quality item it is. Do not invent extra sections; put anything else under Description.
5. Routing - evidence decides, four escalations stay human
Size describes the work and bounds what a round may build, but it does not
decide who approves. The Evaluation of ยง4.10 does, after the ladder of ยง4.11
has been climbed. The history of this section is four routing rules on one
operator's deck: reward/risk asked "how dangerous is the edit?"; net delta
asked "does it sound better?" and passed 85 of 149 on stories; measured
evaluation (v2.5) asked "is it measurably better?" and could say NO - but it
sent every unmeasurable and every contract or policy change to a human, and
under --develop and --optimize that was the whole output: 21 of 46
uncertain items became unmeasurable and zero became better, because
nobody had tried to build the measurement. This rule (v3.0) asks "did you
TRY to measure it, and what did the instrument say?" - and reserves the human
for the decisions a human actually owns. It is the same move /intake made
in v2.5 when its ledger showed 134 of 149 human gates changed nothing: the
run's own read was already selecting, so make the read explicit, veto-first
and auditable, and narrow the ask to the escalations.
Classify every candidate by size for the build bound:
- S - localized: one file, one mechanism (a rename, a guard, an attribute, a clamp, one component's states).
- M - a few files or one subsystem seam; a normal PR.
- L - structural: architecture-grade work spanning modules - new layers, protocol redesigns, cross-cutting migrations.
The evaluation rule
| Result | Gate | Route |
|---|---|---|
better |
none / policy-tighten / contract with its verifier green |
Auto-accept. Build it - S or M, any strategy - in-session, or by a coordinator subagent in a wave. |
better |
direction / architecture / irreversible / policy-loosen / contract without its verifier |
Human. The benefit is proven; the decision is one the run does not own. The card names WHICH escalation. |
not-better |
any | Reject, with the Before/After figures as the rejection reason. It leaves the backlog. |
unmeasurable |
any | Human, and only after ยง4.11: the card names the highest rung attempted, why the next one was out of reach, and the instrument that would decide it. |
The gate column is an ESCALATION list, not a danger list. A change is escalated when the decision belongs to someone other than the run - because it changes what the product IS, cannot be undone, or weakens a protection - never because it is merely large or merely touches something important. Important things that are verifiable are exactly what a sweep is for.
What counts as a measurement. A count (sites, files, keys, renders,
IPC calls), a duration or a size, a reproduced failure and its absence, a
test that goes red-then-green, a gate's exit code, or a walked sample of
concrete inputs with their outputs before and after. "Cleaner", "more
maintainable", "safer" with no figure is not a measurement; if you cannot
attach one, the result is unmeasurable, not better. A probe on a SMALL
sample is enough - three call sites, one reproduced flow, one timed path -
as long as it is the same sample on both sides.
The four escalations - an escalation applies when the implementation REQUIRES it, not when the finding merely mentions it. Design and feature are not escalations: a feature inside scope with a passing test is coverage.
- direction - a capability the context's declared scope does not name:
the manifest's
scopeblock when the repo carries one, else the context map entry's description and keywords. A one-line change that creates a capability the scope does not name is a direction; a two-hundred-line build inside the declared purpose is coverage. This is--develop's human gate, and it is the one/intakekept too (its E1). - architecture - every L: a new layer, a protocol redesign, a cross-cutting migration, an edit to the context map itself. Human even with a figure attached - the largest change in the round is the one the owner sees, by definition.
- irreversible - deletes user data, rewrites stored history, a migration with no rollback, or changes what is paid for.
- policy-loosen - removes or weakens a security, privacy or audit check,
widens access, or stores or sends MORE than before. Its mirror,
policy-tighten- adds a check, narrows access, logs or audits more - is auto-accepted when a test pins the new behaviour, because a reviewer would only ever say yes to it and the test is the review.
contract is evidence-gated, not human by default. A DB schema, IPC or
public API, generated binding or persisted format builds in-round when ALL of:
(a) every consumer is enumerated by instrument (grep for the shared symbol,
the generated client's call sites, the migration's readers) and updated in
the same commit; (b) generated artefacts are regenerated by the repo's own
step; (c) a contract test pins the new shape AND forbids the old (ยง7.5); (d)
the gate is green. Missing any one, it is human and the card says which
letter. A contract another REPOSITORY consumes is always human - the
consumer is not in this tree and (a) cannot be done.
Size still bounds the build. An L is architecture and stays human;
an auto-accepted M that grows past its seam mid-build is demoted like any
other (ยง7.4). Effort / impact / risk are still scored - they order the queue
and calibrate the delta - they just no longer gate it.
The mechanism measures itself. The snapshot (ยง10) carries
auto=<accepted>/<rejected>/<escalated> and fp=<n>, where a false positive
is an auto-accepted item that an executor demoted or that failed its gate at
build. Three fp across the last five rounds on a repo and auto-accept
requires Method gate - a probe no longer suffices - until three consecutive
rounds at fp=0. This is /intake's fp rule carried over: a gate that
never counts its own false positives drifts toward accepting everything.
So does the repo's own mechanical gate, and it binds on the SITE, not on the
idea. A ratchet - a LOC ceiling on a file, a bundle-size budget, a dependency
ban, a coverage floor - can make a better + no-hard-gate finding unbuildable
purely because the only file its fix belongs in is already at the line. That is
not a reason to shrink the fix until it slips through, and not a reason to
invent a new file to escape the rule (a new path is usually outside the
context's declared file_paths, which is veto 1, and often trips an
unmapped-file ratchet as well). Before choosing the build list, check each
candidate's implementation site against the gates the repo declares, and route
the blocked ones to the backlog naming THAT gate as the blocker - the operator
needs to see "correct, safe, and blocked by A1", not a silent absence. Measured
2026-08-29: in one context, six findings were better with no hard gate and only
three had a file with headroom.
Pure churn measures the same on both sides and is therefore not-better
- rejected, with the identical figures as the reason. That is the intent.
Two demotions are a measurement. An idea that two independent executors
turned back (each with a reason) has been measured twice at the point of
build with the same result: it does not land. Treat it as not-better -
reject it with both reasons attached - rather than re-queuing it for a third
attempt. Measured 2026-08-29: three ideas reached a third executor and were
demoted a third time, at ~200k tokens each.
Name the missing instrument - then build it when it is small.
unmeasurable covers two different things: a benefit that has no figure
(taste, product direction), and a benefit that HAS a figure nobody can take
yet (a performance claim with no benchmark, a resilience claim with no fault
injector). For the second kind the rule is ยง4.11: if the instrument is
S-sized, build it in-round as its own fix and re-measure; if it is not, say
which instrument is missing in the Evaluation's After line - that sentence is
a finding of its own for the next round. Measured 2026-08-29 on kp: two of
three unmeasurable results were avoidable, each by a small instrument the
round had budget for.
The four vetoes - they override every route above
An item is backlogged regardless of size or result when it:
- touches a file outside this context's declared paths (see the parallel rules in ยง7) - not yours to change this round;
- changes a contract whose consumers you cannot enumerate by instrument
- another repository's, a public SDK's, a wire format with readers outside this tree. An in-tree contract with its verifier green is not vetoed; see the contract rule above;
- has no gate that can verify it and none can be built at S - if nothing in the repo can tell you the fix worked, you are committing a belief. A missing test you could write in twenty lines is not this veto; write it;
- is a foreign session's in-flight file - a coordination call, not a triage call. Say so in the finding so the next session knows the difference.
Unattended runs (dispatched by an app or a fleet, no operator present):
nothing changes - there is no "ask" band. better with no escalation builds,
not-better is rejected with its figures, everything else waits for the deck.
carry - approved and not built is a state, and it goes first next time.
A better item with no escalation that the round could not build (budget,
time, a tree that would not take a commit) is emitted with
disposition: carry, never as a plain backlog card - it needs no decision.
The next round on the same context builds its carries BEFORE it scans, and
counts them in fixed. Measured 2026-09-01 on pof: a --one round left
approved items unbuilt and the next run presented one as a discovery.
What the backlog is FOR - and what never goes in it. The Personas idea
backlog (the memory outbox โ dev_ideas โ the Quick Answer triage deck) is the
surface where a HUMAN or Athena decides. It holds exactly three things: the
escalated band (direction, architecture, irreversible,
policy-loosen, unverifiable contract), the unmeasurable-after-ladder
band with its named instrument, and anything a veto turned back. An item the
routing table already approved does not need a decision and must not be
parked there - it is executed, in this CLI session:
by the sweep itself in a single-context round, or, in a coordinator wave
(many contexts, workers that return results instead of committing), by
subagents the coordinator dispatches from the returned list, one context per
subagent, each re-checking vetoes 2 and 3 and RE-MEASURING the Evaluation's
Before on the real tree before building, and demoting to the backlog what
fails them. Vetoes 1 and 4 are parallel-session vetoes: under a single
coordinator they do not bind, and an S a worker turned back only for
"outside my paths" or "shared surface" is still an S. A backlog full of
approved-but-unbuilt items is a sweep that stopped one step early - measured
2026-08-28: 149 of 240 backlogged wave findings were auto-approvable by the
sweep's own table.
6. The registry lane - knowledge feeds the lenses, deviations are one lens, leads flow back
Skip this section entirely when the repo declares no registry. When
.ai/manifest.yaml carries registry.local (or registry.remote) and
knowledge.domains, the repo consumes a shared knowledge corpus, and a sweep is
one of the few moments that can pay into it as well as read from it.
Read side - before you judge (ยง4.3). Resolve the subject governing this
context and read its golden path plus the techniques whose use_when matches.
This read is what the LENSES consume (ยง3): each lens takes the techniques that
touch its concern into its own judging. The read is not itself a lens, and a
list of "we deviate from technique X" is not a sweep - it is the output of ONE
lens, registry-conformance, budgeted like every other one.
- If
.ai/registry-map.jsonexists it already holds the contextโsubject join; take the subject'sfileverbatim from the index. Never construct a path from a slug - bundles are nested and depth is dynamic, so a built path points at a folder nobody walks. - Without a map, resolve through
<registry>/knowledge/<domain>/index.json. - Without either, say so in the header (
registry: declared, unmapped) and judge on the repo's own conventions. Degrade honestly; never invent a standard.
A finding that names the technique it violates is worth more than one that names a smell, and it arrives with the fix already described.
Log the consult - append one line to .ai/consults.jsonl:
{"ts":"<ISO>","bundle":"<domain>","subjects":["<slug>"],"techniques":["<slug>"],"deviations":<n>}
deviations counts findings in this round that a technique explicitly names as a
failure. Slugs and counts only, never paths. This is the demand signal the
registry reads to learn which knowledge is actually reached for.
Write side โ after a fix LANDS. Most rounds produce nothing here, and that is the expected result. A lead is owed only when a landed fix (or an L finding you are backlogging) clears all four bars:
- General โ an unrelated team on a different stack would face the same problem. If it depends on this repo's file layout, it is a project lesson, not a lead.
- Rule-shaped โ it states as "when X, do Y, because Z". A description with no trigger has not finished becoming knowledge.
- Novel against the corpus โ you read the governing subject this round and the rule is not already in it. A lead that restates a technique is noise.
- Earned โ it came from code you changed and verified, not from a fix you proposed. A sweep learns by landing things.
Append it to .ai/registry-leads.jsonl:
{"ts":"<ISO>","bundle":"<domain>","nearest":"<subject-slug or null>","kind":"technique|application|subject","claim":"<when X, do Y, because Z - one sentence>","because":"<what this run measured or broke and fixed>","confidence":"low|medium|high","from":"scan-sweep@<version>"}
This is a LEAD, not a knowledge write. A sweep ORIGINATES a finding; it never AUTHORIZES one. Nothing here edits a bundle โ the registry's own intake triages leads, corroborates them, and lands only what survives. Say in the report that you filed one, and say plainly when you filed none.
7. Execution โ land the approved list
Work the approved queue - this context's carry items from prior rounds
first, then this round's, highest-reward first - one finding at a time:
-
One atomic commit per finding. Fix, verify, commit, then start the next. Never stack two findings' edits in one working state. Re-measure the Evaluation's After on the real change before committing, and put the re-measured figure in the commit body: a probe on three sites is what routed the item, the gate on the whole change is what lands it. An After that comes back
not-betteron the real change is a demotion (ยง7.4) and a false positive for the snapshot'sfpcount. -
Verify before committing with the repo's own gates for the surface you touched (
.claude/conventions.jsonor the manifest's capabilities name them; else the obvious ones). A fix that fails its gate is repaired inline or fully reverted โ never committed red, never left half-applied. A gate you could not run is a gate that did not pass: say so in the report and mark the round degraded.ASSERT the gate's own exit code. Reading it is not asserting it, and piping it away destroys it. Three ways this rule has actually been defeated, all in one session, each by the shell rather than by the code:
npm run typecheck | tail -3 && git committakestail's status, always 0. The chain commits over a red gate and the failure scrolls past in the output you were trimming.npm run typecheck; echo "TC=$?"; git commitprints the failure and commits anyway. A number in the transcript is not a gate;;is not&&.- The gate is red for a reason that is not yours โ see below.
- The gate is a composite script whose early stage failed, so the later
stages never ran.
check = typecheck && lint && buildwith a foreign lint error means the build was never executed, and "the red is not mine" is only half the analysis: you still owe the skipped stage. Run it on its own and say which stages actually passed, or the round is degraded.
The shape that holds: run each gate in its own invocation,
&&-chained so a non-zero status stops everything, and let the commit be the last link.Under a concurrent session, a whole-tree gate says nothing about your change.
tsc --noEmitcovers every file in the repository, so a sibling agent mid-write turns your verification red and a passing run can equally depend on their unfinished work. Before treating a red whole-tree gate as yours, get the failing paths and compare them against the files you touched; if none of them are yours, say so, verify what you can scope to your own files, and wait for the tree to settle rather than committing into it. ยง7's parallel rules cover STAGING and stopped there โ verification has the same hazard and it is easier to miss, because the output looks like a verdict on you. -
Commit message
fix(<context>): <finding title>, with a body line naming the lens โ the finding's provenance survives in history. -
A fix that grows beyond its size class mid-flight is demoted, not forced. If an S starts touching a third file or a shared surface, stop, revert the attempt, and emit it as a finding at its honest larger size. This is the safety valve behind auto-approving S: the class is enforced at execution time, not just at classification time.
-
Contract and source-guard tests pin the OLD expression, so a real fix fails them. That is correct by design. Budget for it, and strengthen the assertion in the same commit (pin the new expression AND forbid the old), or the fix silently loses its guard.
-
Never author a pattern through a shell heredoc, and seed a violation to prove the gate still bites. A heredoc reads
\b,\s,\nas its own escapes, so a regex written that way can reach the file as control characters โ measured:\barrived as a literal0x08, the matcher found nothing, and every subject read as compliant. Use the file-writing tool for anything containing a backslash, and after ANY scripted edit to a checker, seed the thing it looks for and watch it go red. A gate that cannot match reports a clean codebase in a voice indistinguishable from success. -
A source-scanning gate must strip comments before it matches. The files a sweep writes explain the rule in prose, directly above the code that implements it โ so a matcher run over raw text is satisfied by a file that TALKS about the rule and does not follow it. Measured twice in one session: a probe passed against a deliberately broken subject because the word it looked for survived in the comment describing the fix. Strip
//and/* */first, then match, and let the fail-before be what tells you โ it is the only step that catches this, and it caught it both times.
Parallel-session rules โ several sweeps may share this repo, one context each:
- Edit ONLY inside your context's paths, plus their tests and any generated artifacts the repo REQUIRES you to regenerate for those edits. A needed change outside that boundary is veto 1, not a decision.
- Stage with explicit pathspecs only. Never
git add -A/./-u, nevergit stash, never reset another session's work. Before each commit confirm the staged list is exactly your files. - Shared or generated surfaces other sessions also write (locale bundles, generated types, checksum manifests): make the edit and its regen, commit IMMEDIATELY, and keep that commit minimal. Shared files must never sit uncommitted while you work on the next finding.
8. Report each round
Header first:
Method: full (context: <name>, strategy: <s>, lenses: <n> evaluated)โ orDEGRADED: <what was skipped and why>if you sampled, skipped a lens, or could not run a gate. A degraded sweep reported as complete is worse than no sweep.registry: <domain>/<subject>โ ornone/declared, unmapped.
Then what SHIPPED, one line each (fixed <title> - <sha> - <re-measured After>), then what was REJECTED (rejected <title> - <Before> vs <After>),
then the carried and backlogged findings โ each in the standard form of ยง4.10
(Summary / Description / Flow / Expected impact / Evaluation, evidence
separate), plus Scores (size + effort / impact / risk) and, for every
backlogged card, the escalation or the failed rung that put it there.
Close each round with: X built (of which carries), Y rejected, Z backlogged
(escalated / unmeasurable / vetoed / carry), lenses evaluated, auto= and
fp=, leads filed, the trend for this context (12 -> 7 -> 5 findings), and
the next context the loop will take.
9. Emit to the memory outbox
Append to the outbox (overlay key memoryOutbox, default
.personas/memory-outbox.jsonl), ONE JSON object per line.
A BUILT finding is a progress node, not a finding โ it must never land in the backlog as open work:
{"type":"node","kind":"progress","skill":"scan-<lens-key>","context":"<context>","title":"Fixed: <title>","body":"<sha>; <one-line gist>"}
Each BACKLOGGED finding:
{"type":"finding","skill":"scan-sweep","lens":"<lens-key>","context":"<context>","title":"<title>","body":"## Summary\n...\n\n## Description\n...\n\n## Flow\n- ...\n\n## Expected impact\n...\n\n## Evaluation\nClaim: performance - ...\nBefore: ...\nAfter: ...\nMethod: probe - ...\nResult: better\nGate: none","evidence":"<code block or file:line list - the proof, not the prose>","size":"S|M|L","effort":3,"impact":7,"risk":2,"result":"better|not-better|unmeasurable","method":"gate|probe|experiment|simulation","gate":"none|contract|policy-tighten|policy-loosen|irreversible|architecture|direction","disposition":"backlog|carry"}
body is the ยง4.10 form verbatim โ the five ## sections, newline-escaped in
the JSON; result, method and gate repeat the Evaluation's verdict as
fields so a consumer can route without parsing prose. A backlog finding is,
by construction, one whose result or gate said "human" โ say which, on the
card. A carry finding is approved and owed to the next round on this
context, and a consumer must not present it for a decision. A not-better
finding is never emitted: it was rejected in the report. A finding emitted in
any other shape is rejected at review, not reformatted.
Escalation โ at most one per lens, ONLY when that lens produced a critical finding (impact >= 8) or 3 real findings in this context:
{"type":"escalation","skill":"scan-sweep","lens":"<lens-key>","context":"<context>","reason":"<<=120 chars: what a deep pass should chase>"}
Coverage โ one node per lens actually evaluated (clean lenses included: that IS the coverage record), plus one for the round:
{"type":"node","kind":"progress","skill":"scan-<lens-key>","context":"<context>","title":"Sweep pass: <lens-key> over <context>","body":"<n> findings; <gist or 'clean'>"}
{"type":"node","kind":"progress","skill":"scan-sweep","context":"<context>","title":"Sweep of <context>","body":"<n> lenses; <built> built, <open> backlogged, <e> escalations, <l> leads"}
Keep the outbox lean โ ingest caps at 200 lines / 512 KB and 30 finding lines per pass. At 12 findings per context that is between two and three rounds, so the loop WILL meet this cap: check whether the file was drained between rounds (the app deletes it on ingest), keep going if it was, and if it was not, emit the round's findings highest-reward first, stop at the cap, and say in the report which findings did not fit and that they are unrecorded. A finding silently dropped for want of a line is worse than one never found, because the ledger will claim the context was swept.
10. Persist a snapshot
Append one line per round to .claude/scan-history/scan-sweep.jsonl (create the
directory if needed). lens_keys = every lens actually evaluated โ this is the
per-context coverage ledger the picker and the lens ordering both read.
findings counts built, rejected, carried and backlogged. auto is the
routing tally (accepted/rejected/escalated), fp the auto-accepted items
demoted at build, carried the approved items left for the next round - the
three numbers ยง5's self-correction reads.
{"at":"<ISO-8601>","scope":"<context>","mode":"resolve|ideas","strategy":"stabilize|develop|optimize","lens_keys":["<key>"],"lenses":<n>,"findings":<n>,"fixed":<n>,"auto":"<a>/<r>/<e>","fp":<n>,"carried":<n>,"escalations":<n>,"leads":<n>,"degraded":<bool>,"note":"<<=80 chars>"}
Project overlay
Everything project-specific lives in .claude/scan-sweep/config.md in the
consuming repo. The skill runs on the defaults without it.
| Key | Default | Meaning |
|---|---|---|
contextMap |
context-map.json |
The context inventory the loop walks. |
memoryOutbox |
.personas/memory-outbox.jsonl |
Where findings are emitted. |
backlogDigest |
.personas/backlog-digest.json |
Titles never to re-propose. |
gates |
from .claude/conventions.json / manifest capabilities |
Verification commands per surface. |
depth |
5 (loop), 10 (--one) |
Findings per context per round. |
neverSweep |
none | Contexts the loop skips (generated, vendored). |
Coverage table
--coverage scans nothing. Run node ${CLAUDE_SKILL_DIR}/scripts/coverage.mjs
(--all for every context, --next for just the next pick, --json for a
machine read) and present the per-context table โ lens coverage, findings vs
fixed, last strategy and age, least-covered first. Then stop.
Skill Reflection
After the work, record only useful observations supported by this run. No lesson is a valid result. Reflection inherits the task's authorization; it grants no additional permission to edit another repository, send data, commit, or publish.
Project learning. Put a dated observation in the consuming project's configured
overlay under ## Skill improvement log, when local edits are within scope. Use the
location in this skill's ## Project overlay section. If none is configured, use
.agents/scan-sweep/config.md for Codex or .claude/scan-sweep/config.md for Claude.
If the harness is unknown, propose the note in the response instead of guessing a path.
Use a supplied memory contract only when its destination and writes are authorized.
Keep project details out of the shared method.
Method learning. Identify the installation before editing anything. A local
.ai/registry-installation.local.json receipt can identify development versus release,
the registry revision, and selected skill versions. Verify any link's actual target;
do not assume a skill directory is a writable registry link.
- For a pinned release, marketplace cache, ordinary copy, or unknown installation, keep a proposal in the project overlay or response. Do not edit the installed method or silently relink it. Adoption and rollback are explicit installation operations.
- For a development link, edit the registry only when that checkout is already within the accepted task scope. Otherwise report a proposal. Authorized changes belong in the source checkout, followed by its gates; commit only when the task authorizes it.
- Record an actual lesson in
LESSONS.mdagainst the version used:## <version-used> - <YYYY-MM-DD> - <project-name>and concise bullets. A proposal must be labeled as such; structural checks are not evidence of field effectiveness. - Applied skill changes require a version bump: patch for wording, minor for a step
refinement, major for method redesign. A lesson alone needs no bump. Shared stamped
clauses are edited in the registry's
docs/skill-clauses/and regenerated withscripts/apply-skill-clauses.mjs, never patched in individual installed skills.
Domain learning. Follow ## Knowledge sync when present, within the same scope
and privacy boundaries. A method lesson and a domain knowledge lead are different
artifacts; do not fabricate either to fill a reflection quota.
Model choice (bake-off 2026-09-01, pof / harness-autonomy)
No pin. Fable built twelve fixes including a feature (durable-sidecar status GET) and junctioned the package's node_modules to run its own gate; Opus built six, all defensive, including the two most severe defects of either run (a self-heal re-verify under a tenth of the gate's buffer; a prior session's output replayed as instruction into a permission-free session), refused to manufacture a gate it could not reproduce, and marked its snapshot degraded. The operator merged both. Fable for throughput on a --one round; Opus when the context is a control surface for autonomous execution.