agentleFS
ShehabovShehabov/ai-dev-crew/.claude/skills/team-bug-register/SKILL.md

team-bug-register

Skill4 starsChanged 46 days agoMITView on GitHub

How bug-historian keeps BUGS.md, the defect register, and uses it to stop the same defect being made twice. Use when a run opens and the team needs its regression brief (stage 1), when the four reviews have passed and the regression guard must check the change against every known defect and every binding standing rule (stage 7), when a run closes and every defect and agent mistake raised in it must be recorded (stage 12), and whenever the Product Lead or a quality role raises a defect. Covers the BUG-NNNN entry format and every field, why a defect got through each gate, standing rules from SR-13 onward and when a defect generalises into one, repeats and the third-occurrence escalation, detection commands that run with git, grep and node only, agent mistakes as entries, and the Open index with its count check.

The file

Show its rules and text

We split this skill into the rules it gives an agent

30 rules. About 0 are common, 20 are distinctive, and 10 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈20

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 10

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Use the sections as they stand, in this order, and never rename them.

    line 54
  2. Very specific:

    Run paths below are relative to the run folder, .devteam/runs/<run-id>/, or the folder DEVTEAM_RUNS_DIR names where that is set.

    line 73
  3. Distinctive:

    Run this from the project root before you add anything.

    line 80
  4. Distinctive:

    Every entry carries a command that finds the defect if it comes back.

    line 189
  5. Distinctive:

    Before the entry is final, search the register for the same pattern.

    line 195
  6. Distinctive:

    Every entry has one, the brief quotes it wherever the entry binds a run, and the guard runs it.

    line 202
  7. Distinctive:

    Prefer the first kind that can see the defect.

    line 237
  8. Distinctive:

    Every run of a detection at the guard or at a closure is saved with the command at the top and the exit status at the bottom.

    line 255
  9. Distinctive:

    Add a standing rule when all four of these are true:

    line 286
  10. Distinctive:

    No existing rule already says it.

    line 292
  11. Distinctive:

    Leave out everything else.

    line 427
  12. Very specific:

    Run each included entry's detection on the current tree (SR-09) and record its exit status in review.md.

    line 434
  13. Very specific:

    Every downstream agent lists bug-historian/brief.md in its consumed.

    line 456
  14. Distinctive:

    Run its detection exactly as published, over the whole tree rather than only the diff.

    line 487
  15. Distinctive:

    Read the result as clean, fired or not run, from the output and the exit status.

    line 491
  16. Distinctive:

    Check it the way its Detect a breach row says.

    line 500
  17. Distinctive:

    Every entry in the brief was checked by running its detection, with the output saved as evidence, and none fired.

    line 538
  18. Distinctive:

    Every standing rule binding this run was checked, with the check stated and the result recorded, and none was broken.

    line 540
  19. Very specific:

    Only bug-historian records the gate, in the guard's handoff for each round, and the gate sync takes the latest.

    line 552
  20. Very specific:

    Read every one, and list each in consumed.

    line 569
  21. Distinctive:

    Write each new entry by the six steps in Writing an entry, naming in "Why it got through" the gate that should have caught it.

    line 588
  22. Very specific:

    Add a standing rule for every entry that generalises, with the next SR-NN.

    line 590
  23. Distinctive:

    Check every new entry for a repeat, update Repeat offenders, and escalate any pattern at its third occurrence.

    line 591
  24. Very specific:

    Run the Open count check written under Open in BUGS.md, grep -c '^| Status | open |' BUGS.md, and confirm it equals the number of rows in …

    line 608
  25. Distinctive:

    Run every detection you wrote in this pass once more, and the survey, against your own output.

    line 611
  26. Very specific:

    Write record.md, then the handoff.

    line 613
  27. Distinctive:

    When the Product Lead raises a defect in one line, or a quality role reports one after a release, the orchestrator opens a run for it and …

    line 658
  28. Very specific:

    Every candidate left out appears in the candidate table in record.md with its reason, so the decision can be checked.

    line 718
  29. Distinctive:

    After the release, the record pass writes two entries and promotes one waiting finding.

    line 977
  30. Very specific:

    When the Product Lead chooses, the orchestrator logs a decision line in ledger.md.

    line 1144

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

The bug register

BUGS.md at the project root is the register. It holds every defect found in the product and every mistake an agent made while building it, each with the rule it produced and a command that detects it. This skill is how the register is kept and how it is used. bug-historian is the only agent that writes to it.

A repeated defect is worse than a new one. A new defect means something was hard. A repeated defect means the register was written and nobody read it, which is a failure of process rather than of code. Everything below exists to make that second kind rare.


What the register is for

It does three jobs, one at each of bug-historian's passes through a run.

JobPassWhat it prevents
BriefStage 1, before any other agent plansAn agent repeating a defect because nobody told it the defect existed
GuardStage 7, after the four reviewsA known defect shipping because the brief was read and then not honoured
RecordStage 12, after the releaseA defect raised in this run being unknown to the next one

A register that only records is an archive: nobody opens it at the moment it would help. The brief is what turns it into a control, and the guard is what makes the brief binding.

Who writes it

bug-historian writes BUGS.md, and no other agent edits it. Every other role reports, and bug-historian carries the report into the register.

WhoHow a defect reaches the register
The Product LeadAnything, in any form. One line is enough. The orchestrator opens a run for it, and bug-historian fills in every field.
qc-engineerqc-engineer/defects.md, with reproduction steps and an evidence path
qc-leadqc-lead/readiness.md, with the evidence it audited or the probe it ran
The four reviewersTheir findings, and their rejections in the blockers of their handoffs
Any agentblockers and machinery_findings in its handoff
The orchestratorIts utilisation findings in orchestrator/review.md, and the rejections and decisions in ledger.md

The register binds bug-historian too. Its briefs, guard results and entries are held to the same standing rules as everyone else's work, and a detection it publishes that does not run is an agent mistake with bug-historian at fault.


The shape of BUGS.md

Use the sections as they stand, in this order, and never rename them. The brief, the guard and the commands below find them by heading.

SectionHoldsChanged
How to use this fileWho does what with the registerOnly on the Product Lead's decision
Entry formatThe block every entry copies, with an illustrative exampleOnly on the Product Lead's decision
ClassesThe class taxonomyOnly on the Product Lead's decision, because a new class changes what every brief filters on
Standing rulesThe rules table, then one ### SR-NN block per ruleAt the record pass, when an entry generalises
OpenAn index of every open entry, derived from the entriesAt the record pass, rebuilt by command and never edited from memory
EntriesEvery entry ever raised, in id order, open and closed togetherAt the record pass
Raised and not yet registeredReal, reproduced findings waiting for an entry of their ownAt the record pass
Repeat offendersEvery pattern seen twice or moreAt the record pass

Ids never change once given. Entries are BUG-NNNN, four digits, from BUG-0001. Standing rules are SR-NN, two digits. The twelve seed rules that ship with the team hold SR-01 to SR-12, so the first rule this project produces is SR-13. No id is reused, renumbered or deleted, and no entry leaves the file: an entry closes, and it stays.

Run paths below are relative to the run folder, .devteam/runs/<run-id>/, or the folder DEVTEAM_RUNS_DIR names where that is set. Every time and date you write comes from the shell at the moment you write it: date -u +%Y-%m-%dT%H:%M:%SZ for a handoff, date -u +%Y-%m-%d for a register date.

The survey

Run this from the project root before you add anything. It gives the next free ids and the surface and component names already in use. It reads entry headings and table rows, never the prose, so an id mentioned in a sentence does not move the count, and it skips the example inside the fenced block under Entry format.

node -e '
const t = require("fs").readFileSync("BUGS.md", "utf8").replace(/\r/g, "").replace(/^\x60{3}[\s\S]*?^\x60{3}/gm, "");
const next = (re, width) => String(Math.max(0, ...[...t.matchAll(re)].map((m) => Number(m[1]))) + 1).padStart(width, "0");
console.log(`next entry: BUG-${next(/^### BUG-(\d{4})/gm, 4)}, next rule: SR-${next(/^\| SR-(\d{2}) \|/gm, 2)}`);
const tally = (label, re) => { const n = {}; for (const m of t.matchAll(re)) n[m[1]] = (n[m[1]] || 0) + 1; console.log(label + (Object.entries(n).map(([k, v]) => `\n  ${v}  ${k}`).join("") || " none yet")); };
tally("surfaces in use:", /^\| Surface \| (.*?) \|$/gm);
tally("components in use:", /^\| Component \| (.*?) \|$/gm);
'

On the register as it ships, it prints next entry: BUG-0001, next rule: SR-13.


Writing an entry

Six steps, in order. The entry is the unit everything else is built from: the brief quotes it, the guard runs its command, and the Open index counts it.

1. Take the next id

From the survey. Two entries written in the same record pass take consecutive ids in the order you write them.

2. Fill every field

Copy the block under Entry format in BUGS.md exactly. Every field is filled or reads none, and a blank field is a defect in the register.

FieldWhat good looks likeMay be none
Heading### BUG-NNNN · and a sentence-case title that says what went wrong as a user, a caller or the next agent met it. "Invoice totals were a cent short on invoices with many small lines", never "Fix rounding in totals".No
Statusopen or closed, and nothing else, because the Open index and its count check read this rowNo
Raised byThe role that first reported it, or Product LeadNo
Raised onThe date from date -u +%Y-%m-%dNo
RunThe run id it was raised in. A defect raised outside a run is recorded in the run the orchestrator opens for it.No
SurfaceThe area a user or a caller meets, named exactly as earlier entries name it: a route family, an API path, a scheduled job, or for the team's own machinery the file. Check the survey's list before you coin a new name, because the brief filters on this field.No
ComponentThe named unit inside the surface: a function, a module, a policy, a region of a screen, a section of an agent file. A process defect with no single unit names the stage or the gate.No
Agent at faultThe role whose output carried the defect. Where the brief, the spec or an agent file was wrong rather than the implementer, name that role or that file. This is routing, and an inaccurate one sends the next brief to the wrong agent.When no role's output carried it, such as a dependency that broke upstream. Route it through "Who must be briefed".
ClassOne class from the Classes section. When two fit, invariant wins, then security, and otherwise the class of the gate that should have caught it. A gate skipped rather than failed makes it process. Use team-architecture to test an invariant claim against PROJECT.md § Product invariants.No
Severityblocker, major or minor, as BUGS.md defines themNo
EvidenceThe path of the file that proves it, under the run's evidence/ folderNo. A defect raised without evidence is reproduced first, and the reproduction is the evidence.
Fixed inWhere the fix landed (a commit, a branch, or the handoff of the fix round) and the date it closed. For an entry closed because it no longer reproduces, that reason.While open
Repeat ofThe earliest entry with the same patternFor a first occurrence
What happenedTwo or three sentences on what was observed, with the line of output, the response or the message that shows itNo
Why it got throughThe gates it passed, and the hole in each. See step 3.No
The rule this producesThe lesson as a class, and the standing rule that carries it where there is one. See step 4.No. A defect that teaches nothing does not belong in the register.
Who must be briefedEvery role the rule binds, by nameNo
How to detect it next timeA command that runs on this project, what firing looks like, and where it was seen to fire. See step 5.No

Runs are gitignored, so the evidence path may not exist on another clone or a year from now. Quote the line that proves the defect in "What happened", and the line that proves the detection fired in "How to detect it next time". The entry has to stand on its own.

3. Write why it got through

Why it happened is the cause. Why it got through is the process finding, and it is the part of the entry that improves the team rather than one file. Every defect that reached a handoff passed some number of gates that should have caught it. Name every gate it passed, and the hole in each, rather than only the last one.

It passedGateSo the finding is
The author's own reviewnone, it is step 4 of the author's loopThe author's definition of done, or the brief addressed to the author, did not name the check, or named it and the author did not run it. Say which.
tech-architectdesign-authorityThe ADR or the task brief allowed it: a contract field missing, an error case left out, an invariant with no enforcement point. The entry routes to tech-architect.
ux-auditordesignThe audit in team-ux-audit has no check for it, or ran the check at a width, a state or a locale that did not show it. Name the check.
ux-writercopyA string rule in team-copy is missing or was not applied. Name the rule.
peer-reviewerreview-judgementA lens in team-code-review had a hole: problem fit, simplicity, boundaries, failure modes, tests, naming and domain language, or rollout. Name the lens and the question it did not ask.
code-analystreview-defectsNo probe in team-code-analysis reached it. Name the section and the probe that would have fired.
code-stewardreview-readabilityA name, a comment or a shape misled the reader past it, and the standard in team-clean-code did not flag it. Name the rule. Where readability had no bearing on the defect, write that, and the gate is not counted as a miss.
security-analystsecurityThe catalogue in team-security, or the sweep that applies it, has a hole. Name the pass by its number and title.
bug-historianregression-guardThe entry that should have caught it was left out of the brief, because the match was made on the path and missed the class, or its detection could not reach the defect: the wrong language, a plain git grep that skipped untracked files, a shell glob expanded one folder deep, a pathspec that missed the folder. Fix the command, prove it fires, and record the miss as its own agent-behaviour entry with bug-historian at fault.
engineering-leadengineeringThe integration run or the regression scope did not exercise the path. Name the seam.
qc-engineernone, it is the test passThe test matrix has a hole. Name the surface, state, locale, width or error path left untested.
qc-leadqualityThe evidence audit accepted a claim, or the independent pass shared the test pass's blind spot. Name the evidence that was missing.
release-engineerreleaseThe verification after release did not look where the defect was.
No gate, it was found after releaseName the gate that should own it. Where no gate can own it, that is a missing gate, and a class that keeps recurring for want of one goes to the Product Lead.

Where a gate did catch it, write which gate caught it and which earlier step should have. For an agent mistake, that earlier step is nearly always the agent's own step 4.

Where a gate was skipped rather than failed, the class is process, and the entry names the role that should have held it. That is usually the orchestrator, when a stage started before the gate it waits for had passed.

4. Write the rule it produces

An incident note tells the next agent about one file. A rule tells every agent about a class of mistake, and only rules prevent anything.

Incident noteRule
Do not call parseFloat on the invoice amount in the totals moduleEvery amount is held and summed in integer minor units. A floating-point number never holds an amount.
The project list forgot to filter archived projectsEvery list query states its filter on archived rows in the query, never in the client after the fetch.
code-analyst dropped --untracked from the briefed commandA briefed detection is run exactly as published. A shortened command is a different check, with an unknown result.

A good rule is written as a class, can be checked, and names the roles it binds. Where an existing standing rule already says it, cite that rule and write nothing new. Where it generalises and no rule says it yet, it becomes a standing rule: see Standing rules.

5. Write the detection

Every entry carries a command that finds the defect if it comes back. The rules for writing one are in Detection commands, and they are strict, because a detection that cannot fire looks like coverage and provides none.

6. Check for a repeat

Before the entry is final, search the register for the same pattern. How, and what follows when you find one, is in Repeats and the third occurrence.


Detection commands

Every entry has one, the brief quotes it wherever the entry binds a run, and the guard runs it. A detection earns its place only if it runs on this project exactly as written.

The rules:

  • It runs with git, grep and node, which are always present, or with a command from PROJECT.md § Commands, such as the db test. A tool PROJECT.md § Toolchain does not list is never used. A detection that needs one is rewritten before the entry is written.
  • It targets the language the surface is written in, from PROJECT.md § Stack. A search for another stack's syntax returns nothing for the wrong reason.
  • It searches recursively in a form that reaches every file: git grep --untracked or grep -rn. Plain git grep skips files not yet committed, which is every file a run has just added. Some shells expand a ** glob one folder deep. Quote every git pathspec so the shell never expands it: to git, 'src/*.ts' matches .ts files at every depth under src/.
  • A search never targets the runs folder with git grep: it is gitignored, and --untracked still skips ignored files. Search run artefacts with grep -rn.
  • It says what firing looks like. For a search, any line of output is a hit and empty output is clean. For a script or a test, the entry names the line that fails.
  • It carries no placeholder. The one parameter allowed is the run folder, written <run-dir>, which the guard fills with the current run's folder, as the utilisation check takes it. A command with any other placeholder is not a check.
  • It has been seen to fire on the unfixed state, and to return nothing on the fixed one, before the entry closes (SR-05).

git grep reports through its exit status as well as its output, and all three values mean something different.

ExitOutputMeans
0One or more linesFired. The pattern is present.
1NothingClean
128, or anything elseAn errorThe command did not run. That is neither fired nor clean, and a check that did not run is an unchecked rule.

Kinds of detection

Prefer the first kind that can see the defect.

KindUse it forForm
A search that must return nothingA banned call, a literal value, a pattern in code or in a documentgit grep --untracked -nE with the pattern and quoted pathspecs
The same search on the unfixed stateProving a detection fires before an entry closesgit grep -nE with the pattern, then a commit or the run's base ref, then the pathspecs. Git reads the committed tree, and nothing in the working tree changes.
A node scriptStructure a search cannot judge: two copies that must match, a count that must agree, a field every file must carrynode -e, printing each hit and nothing when clean
A testBehaviour: an access rule, a state transition, a rounding pathThe test command from PROJECT.md § Commands, naming the test file, the test, and the line that fails on the unfixed code
A narrowed readingA defect only a reader can judge, such as what an error message disclosesA command that lists exactly the lines to read, and the question to ask of each hit. Use it last: a reading repeats only as well as its reader does.

Two practical notes. Write node detections with regular expression literals and single backslashes: some shells halve a doubled backslash on its way to node, and a pattern built from a string with \\ then silently matches something else. Where a detection needs double quotes or a $ of its own, save it as a script under the run's evidence/regression/ folder and run the script, so the shell cannot alter it.

Saving the output

Every run of a detection at the guard or at a closure is saved with the command at the top and the exit status at the bottom. Paste the command from the entry between the double quotes unchanged, so the log proves that the published command is the one that ran.

RUN=.devteam/runs/2026-10-14-invoice-reminders
cmd="git grep --untracked -nE '(parseFloat|Number)\((.*\.)?(amount|total|subtotal|balance)' -- 'src/*.ts' 'src/*.tsx'"
{ echo "\$ $cmd"; eval "$cmd"; echo "exit: $?"; } > "$RUN/evidence/regression/BUG-0004.log" 2>&1

Where DEVTEAM_RUNS_DIR is set, RUN sits under it instead.


Standing rules

A standing rule is the lesson of one or more entries, stated as a class, and it binds every run that touches what it names. It outranks an agent's instinct, which is the reason it is written down. It does not outrank the brand spec, an ADR or a rule in PROJECT.md § Product invariants: where a standing rule conflicts with one of those, the conflict goes to the Product Lead, and nobody picks a side alone.

The seed and the numbering

The team ships with twelve seed rules, SR-01 to SR-12, in BUGS.md under Standing rules. They come from mistakes that recur on any project, whatever its stack, and they bind from the first run. Their table rows read seed in the From column. The first rule this project produces is SR-13, and each one after it takes the next number from the survey.

When an entry becomes a rule

Add a standing rule when all four of these are true:

  1. The lesson binds beyond the component where it was found. Another surface, another agent or another run could make the same mistake.
  2. It can be stated as a class, in one or two sentences.
  3. A breach can be detected, by a command or a narrowed reading, written in the rule's block.
  4. No existing rule already says it. Where one does, cite that rule in the entry's "The rule this produces" and add nothing: two rules for one lesson is the defect SR-03 describes.

A repeat always produces a rule, if the first occurrence did not already. An agent-behaviour entry nearly always does, because a behaviour repeats on every surface the agent touches.

An entry that breaches an existing standing rule cites the rule, and counts as an occurrence of that rule's pattern. A second entry breaching the same rule is a repeat, whatever components the two touched.

Writing a rule

Two edits, made together. First a row at the end of the table under Standing rules:

| SR-13 | Money never passes through a floating-point number | BUG-0004 | backend-engineer, frontend-engineer, code-analyst, qc-engineer |

Then a block after the last rule block, in the shape the seed rules use:

### SR-13 · Money never passes through a floating-point number

| | |
|---|---|
| Rule | Every amount is held, summed and stored in integer minor units, and formatted for display only at the edge, through the one formatting module. No amount is parsed into, or computed as, a floating-point number. |
| Why | A floating-point sum drifts below the cent, and a display that truncates shows the drift. BUG-0004 put a total a cent short on a customer's invoice. |
| Detect a breach | The detection under BUG-0004. Any line of output is a breach. |

The title is the rule in a few words, as a statement. The Rule row is the class, written so a reader could apply it to code that does not exist yet. The Why row is the cost of breaking it, drawn from the entry. The Detect a breach row runs as written, or names the entry whose detection it uses.

The Binds column names the roles that can break the rule. Write every agent only when every role can. A brief that tells everyone the same thing is read by no one.

Retiring a rule

A rule is never deleted. When a rule conflicts with the brand spec, an ADR or a product invariant, or guards something the product no longer has, escalate it. If the Product Lead retires it, its row stays, its Binds cell reads retired <date>: <reason>, decided in <run-id>, and briefs stop carrying it.


Repeats and the third occurrence

Two occurrences of a pattern make it a repeat. A third is escalated to the Product Lead as a process failure, because by then the rule was written, briefed and guarded, and the mistake was made anyway.

Finding a repeat

At the record pass, search the register for every new entry three ways:

  1. The same class and the same component.
  2. The same shape across components. Read "The rule this produces" of every entry in the same class: two entries whose rules say the same thing are one pattern, whatever files they touched.
  3. The same standing rule breached.

This lists every entry with its class, component and status, one block each, for the first two searches:

grep -nE '^(### BUG-|\| (Class|Component|Status) \|)' BUGS.md

The first block it prints is the example under Entry format, which is not an entry.

Recording a repeat

The new entry's Repeat of names the earliest occurrence. At the second occurrence, the pattern gets a row in Repeat offenders, and every later occurrence adds its id to that row.

ColumnHolds
PatternThe class of mistake in plain words, such as "An amount parsed or summed as a floating-point number". Never "BUG-0004 again".
OccurrencesEvery entry id, oldest first
Standing ruleThe rule the pattern breaches
Statewatching at two occurrences: every brief on a surface the pattern touches carries it. escalated at three or more, while the decision is with the Product Lead. decided once the Product Lead has chosen, with the choice quoted and dated in the rule's block and the ledger line cited.

The third occurrence

Escalate at whichever pass first sees it: the brief, the guard or the record. Set status to escalated and next to product-lead, and carry the decision in decisions_for_product_lead with options and a recommendation, never a bare question. At the guard the gate also fails.

The options are changes to the process, which is why they are the Product Lead's:

OptionWhat it does
Mechanise the ruleA check in the project's own lint or test commands, from PROJECT.md § Commands, that fails on a breach, so nobody has to remember the rule
Remove the way to make the mistakeOne module or one type that owns the concept, such as a money type or a date helper, so the wrong form cannot be written
Change the machineryA line in the agent file or skill of the role that keeps making it, so its own step 4 catches it
Accept the riskKeep guarding, with the cost of a fix round each time written down

The worked example at the end of this skill shows one.


The regression brief (stage 1)

DispatchedFirst, ahead of tech-architect
Blocked byNothing
Producesbug-historian/brief.md
Handoffhandoff.json, stage 1, no gate, next is tech-architect

What to read

  • run.json: the surfaces and components in scope, the planned agents, and omitted with the reason for each.
  • BUGS.md, in full and never by search alone, because patterns live across entries rather than inside one.
  • PROJECT.md § Stack, § Commands, § Toolchain and § Stack pack, so every detection you brief runs on this project.

A plan that names no surface and no component is rejected back to the orchestrator: without them the register cannot be filtered.

What goes in

IncludeWhen
A standing ruleIts Binds names a planned agent, or every agent. One line each, naming the planned agents it binds. A rule whose roles are all omitted is left out, and one line says which rules and why.
An entry, open or closedIts Surface or Component is in scope, or its Class and shape match a change in scope on another path. Data leaking across accounts through a list binds a change to an export.
A live repeat patternIts occurrences touch a surface in scope, or a planned agent was at fault in one of them
A waiting findingIts files are among those the run will change. It is briefed now and promoted to a full entry at this run's record pass.
A section per agentEvery planned agent that a rule or an entry above gives something specific to do. An omitted agent gets none.

Leave out everything else. A brief that lists every defect ever is skipped, and the one that mattered is skipped with it. Where nothing in the register touches the run beyond the rules that bind every agent, say exactly that in one line. A short, honest brief keeps the long ones credible.

Run every detection before you brief it

Run each included entry's detection on the current tree (SR-09) and record its exit status in review.md. On a closed entry it returns nothing. A command that errors is rewritten and proved before it is briefed. A hit on a closed entry means the defect is back before this run has changed anything: brief it as present, with the hits, so tech-architect can bring it into scope or name it as out of scope, and register the occurrence at this run's record pass.

The format

The sections, in this order. The worked example at the end of this skill fills them in.

SectionHolds
Heading# Regression brief · <run-id>, then the surfaces in scope, the planned agents and the omitted ones
Standing rules that bind this runOne row per binding rule: id, rule, the planned agents it binds
Prior defects on these surfacesOne row per included entry: id, status, what, agent at fault, and what it matched on (surface, component or class). Each entry's detection follows the table in a code block of its own, exactly as published, because the pipes in a command break a table row.
Live repeat patternsEach pattern, its occurrences, and what this run must do about it, or one line saying none is live
Waiting findings this run will touchEach row from Raised and not yet registered whose files the run changes
Per agentOne section per agent the brief gives something to do, addressed by name, saying which rule binds it, which entry was its own, and which detection to run before it hands off

Address agents by name: a brief addressed to nobody in particular goes unread. Lead with the standing rules, because they bind regardless of surface.

Every downstream agent lists bug-historian/brief.md in its consumed. If none does, the utilisation check reports UNUSED_OUTPUT against bug-historian, which is what makes the brief binding rather than advisory.


The regression guard (stage 7)

DispatchedAfter the four reviews, before engineering-lead
Blocked byreview-judgement, review-defects, review-readability, security
Producesbug-historian/guard.md, and the command output under evidence/regression/
Handoffhandoff-stage7.json, stage 7, the regression-guard gate

What to read

  • bug-historian/brief.md, your own, which lists everything the guard must check.
  • The build lists the plan names, backend-engineer/files.md and frontend-engineer/files.md, or one of them when the plan was right-sized. Reject a list that names no files, or names a file the diff does not contain.
  • The diff. Where the dispatch names a base ref, git diff --stat <base>... and then git diff <base>... in full. Where it names none, the files in the lists.

The reviewers' findings are optional reading. A reviewer may already have flagged a known defect, and the guard runs every detection anyway.

The procedure

For every entry in the brief:

  1. Run its detection exactly as published, over the whole tree rather than only the diff. A defect anywhere on these surfaces ships with this change.
  2. Save the command and its full output to evidence/regression/<bug-id>.log, in the form under Saving the output.
  3. Read the result as clean, fired or not run, from the output and the exit status.
  4. Where it fired, place each hit. A hit in a file named in the build lists means this run repeated the defect, and the fix belongs to that file's author. A hit in a file this run did not touch means an earlier fix did not reach everywhere (SR-05), and the fix belongs to the original entry's agent at fault. Either way the gate fails.
  5. Count the pattern's occurrences, including this one. At three, escalate now.

For every standing rule that binds this run:

  1. Check it the way its Detect a breach row says.
  2. Where the check is a command, save the output to evidence/regression/<rule-id>.log. Where it is a reading, write in guard.md what you read and what you found.
  3. Record the result as held, broken or unchecked. An unchecked rule fails the gate exactly as a broken one does.

Some rules are checked with the team's own scripts. node .devteam/bin/utilisation-check.mjs <run-dir> is read-only and reports PHANTOM_OUTPUT, NO_TIMING, NEVER_RAN, GATE_SELF_CERTIFIED and UNKNOWN_GATE. Never run .devteam/bin/sync-gates.mjs: it writes run.json, which is the orchestrator's alone.

For every waiting finding in the brief, run its reproduction where it has one, and note in guard.md whether this run fixed it. A waiting finding has no entry yet, so it cannot be repeated and does not decide the gate. The record pass uses your note when it promotes it.

A repeat that a reviewer caught first still counts. If a reviewer flagged a known defect and the author fixed it before the guard ran, the guard runs clean, and the record pass registers the occurrence anyway: the author handed off a defect the brief had named.

A detection that errors is your own defect. Rewrite it in BUGS.md, prove the new command fires on the unfixed state, run it on the change, and record the broken command at the record pass. Never pass the gate on a command that did not run, and never touch the code: the guard reports, and the author repairs (SR-06).

guard.md

One row per entry in the brief and one row per binding rule, including every one that held, each with its evidence. Then the waiting findings, then the verdict in one or two sentences.

A guard run again after a fix round keeps every earlier round in guard.md, newest first, each headed with its round. Its evidence carries the round in the name, such as evidence/regression/BUG-0004-round2.log, and its handoff is handoff-stage7-round<R>.json. Nothing from an earlier round is overwritten.

The gate

regression-guard passes when both of these are true:

  1. Every entry in the brief was checked by running its detection, with the output saved as evidence, and none fired.
  2. Every standing rule binding this run was checked, with the check stated and the result recorded, and none was broken.

This is not a judgement call. The gate never passes because the diff looks careful.

Resultstatusgatesnextblockers
Every entry clean, every rule heldpassedregression-guard, pass, evidence bug-historian/guard.mdengineering-leadnone
A detection fired, or a rule was brokenrejectedregression-guard, fail, evidence bug-historian/guard.mdorchestratorOne per hit. what gives the round, the entry id, and the file and line. why quotes the rule from the original entry. needs is the agent at fault.
A check could not run because a tool is missing or a server is not authorisedblockedregression-guard, failorchestratorneeds is product-lead, naming the tool or the server and the exact error
A pattern reached its third occurrenceescalatedregression-guard, failproduct-leadneeds is product-lead, with the decision in decisions_for_product_lead

Only bug-historian records the gate, in the guard's handoff for each round, and the gate sync takes the latest. The guard's own self-checks go in review.md, never in gates[], where an unlisted name raises UNKNOWN_GATE.


The record (stage 12)

DispatchedAfter the release, as the last pass before the orchestrator closes the run
Blocked byrelease, or quality when the run does not ship
Producesbug-historian/record.md, and the new and changed entries in BUGS.md
Handoffhandoff-stage12.json, stage 12, no gate, next is orchestrator

The sources

Read every one, and list each in consumed. A defect that reached any of them and is not in record.md has been dropped.

SourceWhat it gives
qc-engineer/defects.mdDefects with reproduction steps and evidence
qc-lead/readiness.mdDefects and gaps from the evidence audit and the independent pass
bug-historian/guard.mdEvery repeat the guard found, in every round, and what this run did to each waiting finding
Every handoff in the runblockers, where every rejection is a defect someone handed off; machinery_findings; missing_inputs
orchestrator/review.mdThe utilisation findings: a planned role unused, an output nobody consumed, a claimed input that does not exist
ledger.mdThe rejections and their rounds, and every Product Lead decision and acceptance

The checklist

Work it in this order, and tick each item in record.md.

  1. List every candidate from the sources in a table in record.md, with a decision for each: a new entry, a waiting finding, or not recorded, with the reason from What not to record.
  2. Write each new entry by the six steps in Writing an entry, naming in "Why it got through" the gate that should have caught it.
  3. Add a standing rule for every entry that generalises, with the next SR-NN.
  4. Check every new entry for a repeat, update Repeat offenders, and escalate any pattern at its third occurrence.
  5. Close every open entry this run fixed. An entry closes only after its detection has been seen to fire on the unfixed state and to return nothing on the fixed one. For a search, run it at the run's base ref or at the commit before the fix, then on the current tree. For a test, cite the failing run and the passing run, both captured. Save both halves in evidence/regression/close-<bug-id>.log, fill Fixed in, and set Status to closed.
  6. Close every open entry that no longer reproduces, with the reason in Fixed in and the attempt to reproduce it saved as evidence. Never delete an entry.
  7. Promote every waiting finding whose surface this run worked to a full entry. Its row leaves Raised and not yet registered in the same edit that adds the entry, and the entry's "What happened" says which row it came from. Add every real, reproduced finding whose surface this run did not work to that table, rather than dropping it. The table's row numbers are never reused.
  8. Rebuild the Open index from the entries with the command below. It prints one row per open entry. Add the Needs cell for each from the entry: what has to happen for it to close. When no entry is open, the table has no rows and the line under it reads "No entries are open."
  9. Run the Open count check written under Open in BUGS.md, grep -c '^| Status | open |' BUGS.md, and confirm it equals the number of rows in the Open table. The command below makes the same comparison and names any entry that differs.
  10. Run every detection you wrote in this pass once more, and the survey, against your own output. The register binds you.
  11. Write record.md, then the handoff.

The count in BUGS.md reads the whole file, so it would also count the example under Entry format if that example's Status ever read open. It reads closed, and it stays that way.

This rebuilds the index and checks it in one run, from the project root:

node -e '
const text = require("fs").readFileSync("BUGS.md", "utf8").replace(/\r/g, "").replace(/^\x60{3}[\s\S]*?^\x60{3}/gm, "");
const section = (name) => text.split(/^## /m).find((s) => s.split("\n")[0].trim() === name) || "";
const field = (block, name) => { const line = block.split("\n").map((l) => l.trim()).find((l) => l.startsWith(`| ${name} | `)); return line ? line.slice(name.length + 5, -2).trim() : "missing"; };
const entries = section("Entries").split(/^### /m).slice(1).map((b) => ({ id: b.slice(0, 8), what: b.split("\n")[0].slice(11).trim(), status: field(b, "Status"), surface: field(b, "Surface"), agent: field(b, "Agent at fault") }));
const open = entries.filter((e) => e.status === "open");
const indexed = [...section("Open").matchAll(/^\| (BUG-\d{4}) \|/gm)].map((m) => m[1]);
for (const e of open) console.log(`| ${e.id} | ${e.what} | ${e.surface} | ${e.agent} | |`);
const odd = entries.filter((e) => e.status !== "open" && e.status !== "closed").map((e) => `${e.id} (${e.status})`);
const missing = open.filter((e) => !indexed.includes(e.id)).map((e) => e.id);
const extra = indexed.filter((id) => !open.some((e) => e.id === id));
console.log(`open entries: ${open.length}, Open index rows: ${indexed.length}`);
if (odd.length) console.log(`status neither open nor closed: ${odd.join(", ")}`);
if (missing.length) console.log(`open but not in the index: ${missing.join(", ")}`);
if (extra.length) console.log(`in the index but not open: ${extra.join(", ")}`);
process.exitCode = odd.length + missing.length + extra.length ? 1 : 0;
'

It exits 0 when the index agrees with the entries and 1 when it does not. Paste its output into record.md.

record.md

It lists, each under its own heading: the sources read; the candidate table; every entry added, updated, promoted or closed, with the close log for each closure; every rule added; every change to Repeat offenders; every escalation; and the output of the Open count check. The worked example at the end of this skill shows one.

A pattern at its third occurrence sets the record's handoff to escalated, with next set to product-lead and the decision in decisions_for_product_lead. The entries are still written first: the escalation is about the pattern, and the register stays complete either way.


Recording outside a run

When the Product Lead raises a defect in one line, or a quality role reports one after a release, the orchestrator opens a run for it and dispatches you. Take the report as given and never send it back for more detail. You do the work of filling in the entry: reproduce the defect, save the reproduction as evidence, find the gates it passed, and write the rule and the detection. It is your first handoff in that run, so the file is handoff.json, with the stage of the plan entry it answers.


Agent mistakes

An agent mistake is behaviour: an agent did something its own file, or team-protocol, tells it never to do. It is recorded as a full entry the first time it happens, whether or not a gate caught it. A code defect lives in one file. A behaviour repeats on every surface the agent touches, which makes it the more expensive of the two.

The class is agent-behaviour. Where the mistake was a gate skipped, a handoff unread or a planned role left unused, the class is process.

MistakeLooks likeDetected by
Invented a valueA colour, size, duration or radius the brand spec at the path in PROJECT.md § Brand does not containA search of the brand spec for the value (SR-10)
Cited from memoryA section, token or rule that is missing from the cited file, or says something else thereOpening the cited file (SR-10)
Marked work done without evidencestatus: passed with a produced path that is empty or missingPHANTOM_OUTPUT in the utilisation check (SR-01)
Claimed a false inputA consumed path it did not read, or that does not existFALSE_CONSUMPTION in the utilisation check
Certified a gate it does not ownA gates[] entry for another role's gate, or under a name the plan does not carryGATE_SELF_CERTIFIED and UNKNOWN_GATE (SR-08)
Skipped the plan auditNo heading starting with Audit in plan.md, or an audit with no revisions and no word on why none were neededLOOP_SKIPPED for the first, a reading for the second
Invented a timeReversed times, or a run of round onesNO_TIMING (SR-02)
Overwrote an earlier passA later pass writing to the first pass's handoffNEVER_RAN and MALFORMED_HANDOFF (SR-07)
Narrowed scope silentlyLess than the brief asked for, with no blocker naming the gapThe brief's acceptance criteria read against the handoff and its evidence
Papered over bad inputWorked around a broken handoff instead of rejecting itA gap in an upstream artefact that no blockers entry names
Guessed a PROJECT.md factA value in an artefact that the cited section does not holdA search of PROJECT.md for it
Faked a tool or a resultEvidence from a tool PROJECT.md § Toolchain does not list, or output the command could not have producedRunning the command
Altered a published commandA detection run in a shorter or different form than the brief publishedThe command at the top of the agent's evidence log, compared with the brief
Repaired what it checksA reviewer or a check editing the thing under reviewA reviewer's produced naming a file under review (SR-06)
Dispatched another agentWork that started with no dispatch line for it in ledger.mdledger.md
Added attributionA line PROJECT.md § House rules forbids, on a commit, a comment or a documentA search of the commit messages and the diff for the forbidden form

Where the machinery caused the mistake, because an agent file or a skill was silent, ambiguous or told the agent to do it, the entry's Agent at fault names that file and says so. The fix is a change to the file, and it goes to the Product Lead in decisions_for_product_lead with the change proposed.


What not to record

The register is useful only while it is read, and it stops being read when it is padded.

  • A defect caught and fixed inside one agent's own loop, before any handoff. That is the loop working. Record it only when it reveals a rule nobody has written.
  • A style preference a formatter owns.
  • A one-off failure of the environment with no product cause, such as a mail server that refused connections for four minutes.
  • The same defect reported twice in one run, by two roles. It gets one entry, and "What happened" names both reports. A recurrence in a later run is different: that is a repeat, and it gets its own entry.
  • A suspicion nobody could reproduce. Reproduce it yourself where you can. Where you cannot, it is not recorded, and record.md says why.

Every candidate left out appears in the candidate table in record.md with its reason, so the decision can be checked.


Worked example: one defect through three runs

A billing product with accounts, projects and invoices. One mistake, handling money as a floating-point number, is recorded, briefed, caught on its return, and escalated when it comes back a third time. The paths and the TypeScript are illustrative; on a real project they come from PROJECT.md § Stack.

Run one: the entry and its rule

qc-engineer reports a total a cent short in 2026-10-06-invoice-totals. At that run's record pass, bug-historian writes the entry below, and the SR-13 row and block shown under Writing a rule.

### BUG-0004 · Invoice totals were a cent short on invoices with many small lines

| | |
|---|---|
| Status | closed |
| Raised by | qc-engineer |
| Raised on | 2026-10-06 |
| Run | 2026-10-06-invoice-totals |
| Surface | billing/invoices |
| Component | invoice totals, `src/billing/totals.ts` |
| Agent at fault | backend-engineer |
| Class | correctness |
| Severity | major |
| Evidence | `.devteam/runs/2026-10-06-invoice-totals/evidence/qc/ten-line-invoice.log` |
| Fixed in | `backend-engineer/handoff-stage2-round2.json` of 2026-10-06-invoice-totals, closed 2026-10-07 |
| Repeat of | none |

**What happened.** An invoice with ten lines of 0.10 showed a total of 0.99. Each line was
read with `parseFloat` and summed, the sum came to 0.9999999999999999, and the display cut it
to cents. From the evidence: `total: 0.99, expected: 1.00`.

**Why it got through.** It passed backend-engineer's own review, and the brief had nothing
on money to point it at. It passed peer-reviewer, whose tests lens accepted fixtures priced in
whole units only. It passed code-analyst, because section 1, correctness, has no probe for an
amount held as a floating-point number. code-steward and security-analyst had no bearing on
it. There was no entry yet for the guard to run. qc-engineer caught it with a ten-line
invoice.

**The rule this produces.** Every amount is held and summed in integer minor units, and
formatted only at the edge. A floating-point number never holds an amount. Added as SR-13.

**Who must be briefed.** backend-engineer, frontend-engineer, code-analyst, qc-engineer.

**How to detect it next time.** Stack: TypeScript.
`git grep --untracked -nE '(parseFloat|Number)\((.*\.)?(amount|total|subtotal|balance)' -- 'src/*.ts' 'src/*.tsx'`.
Any line of output is a hit. Seen to fire at the run's base ref, on
`src/billing/totals.ts:2: rows.reduce((s, r) => s + parseFloat(r.amount), 0)`, and clean on
the fixed tree.

Run two: the brief

2026-10-14-invoice-reminders sends reminders for overdue invoices and adds an amount-due column to the invoice list. Every role is planned. The brief, in full:

# Regression brief · 2026-10-14-invoice-reminders

Surfaces this run touches: billing/invoices, billing/reminders, api/invoices.
Planned: every role. Omitted: none.

## Standing rules that bind this run

| # | Rule | Binds in this run |
|---|---|---|
| SR-01 | Evidence is a file, never a claim | every planned agent |
| SR-02 | Timestamps come from the shell | every planned agent |
| SR-03 | One source for every definition, never two copies | tech-architect, orchestrator |
| SR-04 | A count is derived from the list it counts | every planned agent |
| SR-05 | A fix reaches everywhere the defect lives, and its detection is seen to fire first | every planned agent |
| SR-06 | A checker never repairs what it checks | orchestrator, bug-historian, the four reviewers, qc-lead |
| SR-07 | A handoff names its stage, and no pass overwrites another | every planned agent |
| SR-08 | A gate is recorded only by its owner, under a name the plan carries | every gate owner, orchestrator |
| SR-09 | A published command has been run | every planned agent |
| SR-10 | A spec is read at the moment it is cited | every planned agent |
| SR-11 | A reference image never sets a value | ux-designer, ux-auditor, frontend-engineer |
| SR-12 | A spec that contradicts itself goes to the Product Lead | every planned agent |
| SR-13 | Money never passes through a floating-point number | backend-engineer, frontend-engineer, code-analyst, qc-engineer |

## Prior defects on these surfaces

| Id | Status | What | Agent at fault | Matched on |
|---|---|---|---|---|
| BUG-0004 | closed | Invoice totals were a cent short on invoices with many small lines | backend-engineer | surface billing/invoices |
| BUG-0002 | closed | The overdue badge on the project list counted days in the viewer's time zone | frontend-engineer | class and shape: this run decides when an invoice is overdue |

Command 1, for BUG-0004. Any line of output is a hit.

```bash
git grep --untracked -nE '(parseFloat|Number)\((.*\.)?(amount|total|subtotal|balance)' -- 'src/*.ts' 'src/*.tsx'
```

Command 2, for BUG-0002. Any line of output is a hit.

```bash
git grep --untracked -nE 'new Date\(\)\.(getDate|getDay|setHours)\(' -- 'src/*.ts' 'src/*.tsx'
```

Both ran on the current tree before this brief was written: exit 1, clean.

## Live repeat patterns

None of the patterns in Repeat offenders touches these surfaces or these agents.

## Waiting findings this run will touch

| # | Finding | Files | Raised by |
|---|---|---|---|
| 3 | The amount on the invoice PDF puts the currency symbol before the number in every locale | src/billing/format.ts | qc-engineer |

It is promoted to a full entry at this run's record pass.

## Per agent

### tech-architect

SR-13 binds the contract: every amount in api/invoices and in the reminder payload travels in
integer minor units. SR-03: the reminder interval is defined in one place, and the ADR names
that place.

### backend-engineer

SR-13 binds you, and BUG-0004 was yours, on this surface. The reminder job sums what is due,
so it sums minor units. Run command 1 against your change before you hand off, and record the
result in your review.

### frontend-engineer

SR-13 binds you. The amount-due column formats minor units through the formatting module and
never parses an amount. BUG-0002 has the same shape as "overdue": decide it in the account's
time zone, as the contract carries it. Waiting finding 3 is in src/billing/format.ts, which you
will change. Run commands 1 and 2 before you hand off.

### code-analyst

Add commands 1 and 2 to your probe list, and run them exactly as written here. BUG-0004 got
past section 1, correctness, because no probe looked for an amount held as a floating-point
number.

### qc-engineer

The hole BUG-0004 exposed was fixtures priced in whole units. Test an invoice with ten lines of
0.10, and an invoice that falls due at 23:30 in an account east of UTC.

### ux-writer

Waiting finding 3: where the currency symbol sits is a rule per locale, and both the
amount-due header and the reminder email carry amounts.

Every other planned agent is bound by the rules in the table above, with nothing further in
this run.

Run two: the guard fails

frontend-engineer hands off without running command 1. The four reviews pass, and the guard's first round finds the defect in a file the run added.

# Regression guard · 2026-10-14-invoice-reminders

## Round 1

Base ref: `main` at 4be1c07. In scope: the 9 files in backend-engineer/files.md and
frontend-engineer/files.md. Every detection ran over the whole tree.

### Entries

| Id | Result | Hits | Evidence |
|---|---|---|---|
| BUG-0004 | fired, exit 0 | `src/billing/reminder-summary.ts:12`, a file this run added, written by frontend-engineer | evidence/regression/BUG-0004.log |
| BUG-0002 | clean, exit 1 | none | evidence/regression/BUG-0002.log |

### Standing rules

| Rule | How it was checked | Result | Evidence |
|---|---|---|---|
| SR-01 | The utilisation check on this run: no `PHANTOM_OUTPUT` | held | evidence/regression/SR-01.log |
| SR-02 | The same run of the check: no `NO_TIMING` | held | evidence/regression/SR-01.log |
| SR-03 | `git grep --untracked -n 'REMINDER_INTERVAL_DAYS'`: one definition, two uses | held | evidence/regression/SR-03.log |
| SR-04 | The reminder states counted in the ADR and in the migration's enum: three in each | held | evidence/regression/SR-04.log |
| SR-05 | No fix in this run claims to close an entry, so there is no closure to trace; the record pass checks every closure | held | this row |
| SR-06 | The four reviewers' `produced` lists read: findings files only, no file under review | held | evidence/regression/SR-06.log |
| SR-07 | Every agent folder listed: each first handoff still present | held | evidence/regression/SR-07.log |
| SR-08 | The same run of the utilisation check: no `GATE_SELF_CERTIFIED`, no `UNKNOWN_GATE` | held | evidence/regression/SR-01.log |
| SR-09 | This run publishes no command in a skill, an agent file or the register, and both briefed detections ran at the brief | held | bug-historian/review.md |
| SR-10 | Every `PROJECT.md` and brand spec citation in the design spec and the ADR opened: each section exists and says what is cited | held | this row |
| SR-11 | The design spec searched for hex, pixel and duration values, and each hit read for a reference file name in the same row or sentence: none | held | evidence/regression/SR-11.log |
| SR-12 | The brand spec searched for "row height", the one token the spec relied on that two sections could govern: one section governs it | held | evidence/regression/SR-12.log |
| SR-13 | The BUG-0004 detection | broken | evidence/regression/BUG-0004.log |

### Waiting findings

| # | This run | Evidence |
|---|---|---|
| 3 | Not fixed: src/billing/format.ts still places the symbol first in every locale | evidence/regression/waiting-3.log |

### Verdict

Fail. BUG-0004 was repeated in a file this run added, which breaks SR-13, a rule the brief
addressed to frontend-engineer by name. It is the pattern's second occurrence, so it is a
repeat, and not yet an escalation. code-analyst's probe log shows the same detection reported
clean: it ran as plain `git grep`, which skipped the new, untracked file. Both go to the record
pass.

The handoff for that round:

{
  "run": "2026-10-14-invoice-reminders",
  "agent": "bug-historian",
  "stage": 7,
  "status": "rejected",
  "started": "2026-10-15T10:02:11Z",
  "finished": "2026-10-15T10:31:40Z",
  "consumed": [
    "bug-historian/brief.md",
    "backend-engineer/files.md",
    "frontend-engineer/files.md"
  ],
  "produced": [
    "bug-historian/guard.md",
    "evidence/regression/"
  ],
  "gates": [
    { "name": "regression-guard", "result": "fail", "evidence": "bug-historian/guard.md" }
  ],
  "blockers": [
    {
      "what": "Round 1. BUG-0004 repeated at src/billing/reminder-summary.ts:12: const due = Number(invoice.total)",
      "why": "SR-13, from BUG-0004: every amount is held and summed in integer minor units, and no amount is parsed into a floating-point number. The brief addressed this rule and its detection to frontend-engineer by name.",
      "needs": "frontend-engineer"
    }
  ],
  "missing_inputs": [],
  "machinery_findings": [],
  "decisions_for_product_lead": [],
  "next": "orchestrator"
}

The orchestrator routes the fix to frontend-engineer, which writes handoff-stage5-round2.json, and the reviews run again. The guard's second round sits above the first in the same guard.md. Every row is run again, with its evidence named -round2, and every row is clean. Its handoff, handoff-stage7-round2.json, passes regression-guard with next set to engineering-lead.

Run two: the record

After the release, the record pass writes two entries and promotes one waiting finding. The repeat:

### BUG-0009 · The amount-due column read invoice totals as floating-point numbers

| | |
|---|---|
| Status | closed |
| Raised by | bug-historian |
| Raised on | 2026-10-15 |
| Run | 2026-10-14-invoice-reminders |
| Surface | billing/invoices |
| Component | invoice list, amount-due column, `src/billing/reminder-summary.ts` |
| Agent at fault | frontend-engineer |
| Class | correctness |
| Severity | major |
| Evidence | `.devteam/runs/2026-10-14-invoice-reminders/evidence/regression/BUG-0004.log` |
| Fixed in | `frontend-engineer/handoff-stage5-round2.json` of 2026-10-14-invoice-reminders, closed 2026-10-16 |
| Repeat of | BUG-0004 |

**What happened.** The amount-due column read each invoice total with `Number(invoice.total)`
and summed the results. The guard's detection fired on it:
`src/billing/reminder-summary.ts:12:const due = Number(invoice.total)`.

**Why it got through.** The brief addressed SR-13 and the BUG-0004 detection to
frontend-engineer by name, and its review records no run of the detection, so the author's own
review let it through unchecked. It passed peer-reviewer, whose tests lens again accepted
fixtures priced in whole units, the hole BUG-0004 named. It passed code-analyst, whose probe
ran as plain `git grep` and skipped the new, untracked file (BUG-0010). code-steward and
security-analyst had no bearing on it. The guard caught it.

**The rule this produces.** SR-13, already standing. This is its second breach, and the
pattern is in Repeat offenders.

**Who must be briefed.** frontend-engineer, backend-engineer, code-analyst, qc-engineer.

**How to detect it next time.** The detection under BUG-0004, unchanged. It fired in round 1
of the guard, on the line above, and returned nothing in round 2, exit 1.

The agent mistake, recorded as a first-class entry with a rule of its own:

### BUG-0010 · code-analyst reported a briefed detection clean after running a shorter form of it

| | |
|---|---|
| Status | closed |
| Raised by | bug-historian |
| Raised on | 2026-10-15 |
| Run | 2026-10-14-invoice-reminders |
| Surface | billing/invoices |
| Component | code-analyst's probe list, stage 6 |
| Agent at fault | code-analyst |
| Class | agent-behaviour |
| Severity | major |
| Evidence | `.devteam/runs/2026-10-14-invoice-reminders/evidence/code-analyst/probes.log` |
| Fixed in | `code-analyst/handoff-stage6-round2.json` of 2026-10-14-invoice-reminders, closed 2026-10-16 |
| Repeat of | none |

**What happened.** The brief published the BUG-0004 detection with `--untracked`.
code-analyst's probe log shows it ran `git grep -nE` with the same pattern and no
`--untracked`, reported the probe clean, and passed `review-defects`. The file carrying the
defect was new and untracked, so the search never read it.

**Why it got through.** Nothing compares the command in a reviewer's probe log with the
command the brief published. It passed code-analyst's own step 4, which checked that every
probe ran, and not that each ran as written. The guard caught it, by running the published form
and firing on the same file.

**The rule this produces.** A briefed detection is run exactly as published, and its log
starts with a line holding `$` and the command as run. SR-09 covers publishing a command, not
running one, so this is added as SR-14.

**Who must be briefed.** peer-reviewer, code-analyst, code-steward, security-analyst,
qc-engineer, qc-lead, bug-historian.

**How to detect it next time.** Stack: any.
`grep -rn --exclude-dir=regression '^\$ git grep' <run-dir>/evidence/ | grep -v -- '--untracked'`.
Any line of output is a `git grep` run without `--untracked`, to be read against the brief.
Seen to fire on round 1's `evidence/code-analyst/probes.log`, and to print nothing from round
2's `probes-round2.log`.

The record itself:

# Record · 2026-10-14-invoice-reminders

## Sources read

| Source | Found |
|---|---|
| qc-engineer/defects.md | One defect, D-1 |
| qc-lead/readiness.md | No defect beyond D-1 |
| bug-historian/guard.md | One repeat and one altered probe in round 1; round 2 clean; waiting finding 3 not fixed |
| Every handoff | One rejection, the guard's round 1; no machinery findings; no missing inputs |
| orchestrator/review.md | No utilisation findings |
| ledger.md | One reject line; one decision line, in which the Product Lead kept waiting finding 3 out of scope |

## Candidates

| Source | Finding | Decision |
|---|---|---|
| guard.md, round 1 | BUG-0004 repeated in src/billing/reminder-summary.ts | New entry BUG-0009, repeat of BUG-0004 |
| guard.md, round 1 | code-analyst ran the BUG-0004 detection without `--untracked` | New entry BUG-0010, agent-behaviour |
| brief.md, waiting row 3 | The currency symbol placed first in every locale | Promoted to BUG-0011, open. This run worked the surface, and the ledger's decision line kept the fix out of scope. |
| qc-engineer/defects.md, D-1 | The test mail server refused connections for four minutes | Not recorded: an environment failure with no product cause |

## Entries added

BUG-0009, closed in this run. BUG-0010, closed in this run. BUG-0011, open.

## Entries closed

| Id | Fired on the unfixed state | Clean on the fixed state | Close log |
|---|---|---|---|
| BUG-0009 | Guard round 1: one hit, exit 0 | Guard round 2: exit 1 | evidence/regression/close-BUG-0009.log |
| BUG-0010 | Round 1's probes.log: one hit | Nothing from probes-round2.log | evidence/regression/close-BUG-0010.log |

## Rules added

SR-14 · A briefed detection runs exactly as published. From BUG-0010. Binds the four reviewers,
qc-engineer, qc-lead and bug-historian.

## Repeat offenders

New row: "An amount parsed or summed as a floating-point number", BUG-0004 and BUG-0009, SR-13,
watching.

## Escalations

None. The pattern is at its second occurrence.

## Open count check

```text
$ grep -c '^| Status | open |' BUGS.md
1

The rebuild and check command from team-bug-register, run from the project root:
| BUG-0011 | The invoice PDF puts the currency symbol before the amount in every locale | billing/invoices | frontend-engineer | |
open entries: 1, Open index rows: 1
exit: 0
```

Run three: the escalation

In 2026-11-03-credit-notes, the guard fires on the BUG-0004 detection again, in the credit note total. That is BUG-0015, the pattern's third occurrence and SR-13's second breach after it was briefed by name. The guard fails, the Repeat offenders row moves to escalated, and the handoff reads status: escalated and next: product-lead, with this decision:

{
  "question": "An amount parsed as a floating-point number has now happened three times, in BUG-0004, BUG-0009 and BUG-0015, and the last two came after SR-13 was briefed by name to the agent at fault. How should it be stopped?",
  "options": [
    "Mechanise SR-13: a money type that holds minor units, and a rule in the project's lint command that fails on parseFloat or Number over an amount. One run of work, after which the mistake fails the build.",
    "Add the BUG-0004 detection to the definition of done in frontend-engineer.md and backend-engineer.md. No product code, but it still depends on the check being run.",
    "Accept the risk and keep guarding. The guard caught the last two, at the cost of a fix round each."
  ],
  "recommendation": "Mechanise it. Two breaches after two briefs show that reading does not hold this rule, and a check the build runs does not depend on anyone remembering it."
}

When the Product Lead chooses, the orchestrator logs a decision line in ledger.md. At the record pass, the Repeat offenders row moves to decided, and the SR-13 block quotes the choice with its date and cites that ledger line.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.