2026-08-30: the em dash gate unblocked, and the shared-index rule given a home โ
- Status: done, 2026-08-30 09:47 PDT.
- Branch:
feat/admin-and-merchant-portals, draft PR#976. - Files:
tooling/scripts/lint.no-em-dash.js,.agents/skills/multi-agent/skill.mdplus its five generated copies.
What was broken? โ
npm run lint:emdash exited 1 on this branch, so npm run validate failed for every lane on it. โ
- 27 files, all under
docs/projects/brainstorm-archive/mockups/, 117 occurrences in total. Nothing else on the branch was above its baseline. - Those are design and architecture mockups from brainstorm sessions between 2026-03-29 and 2026-05-26, rescued verbatim from a gitignored
.superpowers/folder before the plugin that produced them was uninstalled. - Preserving them byte for byte is the entire point.
docs/projects/brainstorm-archive/README.mdrecords a checksum-verified verbatim guarantee, so rewriting one to satisfy the gate would destroy the thing the archive exists to hold.
What changed in the linter? โ
One EXCLUDE entry, beside the existing docs/projects/*/harness/*.json exemption, for the same reason. โ
- The pattern added to
tooling/scripts/lint.no-em-dash.js:/^docs\/projects\/brainstorm-archive\/mockups\//. - The comment above it says WHY in the same shape as its neighbour: preserved historical artifacts with no source to fix.
- The exclusion stops at
mockups/. The archive's ownREADME.mdsits one level up, is still scanned, and is still enforced at its baseline of zero, so any prose we actually author about the archive stays under the rule.
Did the two ratchet checks pass? โ
Both are required by .agents/rules/project/linter-organization.md before any ratchet baseline is loosened, and they catch different failures.
DIRECTION: can this hide a real violation? No, and the path was checked rather than assumed. โ
- The excluded path holds exactly 28 tracked files, all
.html, all of them archived mockups. There is no.jsx, no.js, and no authored.mdunder it. - No shipped UI lives there.
isUiSourceFile()only classifies paths underapps/*/src/orpackages/ui/, so nothing in this directory ever reached the zero-tolerance UI-copy check in the first place. The exclusion cannot weaken that check. - No authored prose lives there either. Every file is byte-identical to a machine-served capture from a dead session, and no file was edited on the way in. The one authored document about the archive, its
README.md, is outside the excluded path and stays scanned. - Nothing else in the repo can slip in behind it. The pattern is anchored with
^to one literal directory, not a*wildcard across projects, so a future project cannot acquire the exemption by naming a foldermockups. - The baseline file itself was not touched. No ceiling moved up; one directory left the scan entirely.
MEANING: would the new ceiling fire on correct behaviour? Yes, before the change, and that is the defect being fixed. โ
- Correct behaviour for these files is to leave them alone. The gate demanded an edit that the archive's own verbatim guarantee forbids, so it fired on the correct action and had no clean resolution inside the rule.
- A gate that fires on correct behaviour is worse than a loose one, because it gets ignored, then switched off. This one was already blocking
npm run validatefor every lane on the branch, which is that failure starting. - After the change the gate has no way to fire on correct behaviour in this directory, because there is no correct behaviour in it other than leaving the files as they are.
Was the gate proved still alive? โ
Yes, exit code before and after, plus a control that still fails. โ
| Run | Result |
|---|---|
npm run lint:emdash before the change | exit 1, 27 files listed, all under brainstorm-archive/mockups/ |
npm run lint:emdash after the change | exit 0, 704 baseline files, 6938 grandfathered occurrences, 0 confirmed UI-copy hits |
Control: one new em dash appended to docs/projects/brainstorm-archive/README.md | exit 1, README.md: 1 occurrence(s) (baseline allows 0) |
| After restoring the control file | exit 0, git status clean on that path |
- The control was deliberately placed inside the archive but outside
mockups/, so it proves two things at once: the gate still catches a new em dash, and the exclusion did not leak up to the archive root. - The control file was restored from a scratchpad copy, never with a
gitcommand, so no index state was touched.
What went into the multi-agent skill? โ
The shared-index rule, in the parallel-git section, which covered dispatched sub-branches and said nothing about lanes sharing one working tree. โ
- What happened on 2026-08-30: a lane staged two exact paths, ran a bare
git commit, and swept in 29 files another lane had staged but not yet committed, under its own message. The content survived, the provenance did not. - The mechanism is the bare commit, not
git add -A. A baregit committakes the WHOLE index, and the index is shared, so how precisely you staged your own paths does not enter into it. The lane had obeyed "nevergit add -A" exactly and reproduced the incident anyway. - The fix is the pathspec form,
git commit -- <paths>, which commits only those paths whatever else sits in the index, and leaves a peer's staged work where they put it.-A,git add .andcommit -astay forbidden, since they sweep in a peer's UNSTAGED work as well, but they are the easy half. - Never leave files staged between commands either, because the gap is a window a peer's bare commit takes your staged file through.
- Why it needed a durable home: "never
git add -A" was in bold in every dispatch prompt that day, and the lane that obeyed it still lost the files. A prose constraint in a prompt has now failed once, and it failed through a command the prompt never named, which is the argument for the skill carrying the mechanism rather than one shape of it.
The first version of this rule named -A as the cause, which was wrong, and a lane reading it would have reproduced the incident. โ
- The case history reached this lane as "a lane ran
git add -A", relayed in good faith, and shipped that way in commite7b4acf4. The lane that caused it reported the real sequence afterwards. - Corrected in a follow-up commit rather than an amend, since
e7b4acf4was already on the remote. - The wrong version was worse than no rule for the exact case it described: an agent that reads "never
-A", obeys it, and types a baregit commitgets the full incident with a clean conscience.
Both halves were then reproduced in a scratch repo, so the taught commands are verified rather than reasoned. โ
- The failure: with a peer's
peer.jsstaged alongside mymine.js, a baregit commit -m ...produced a commit containing both. - The fix:
git add new.js && git commit -m ... -- new.js mine.jsproduced a commit containing exactly those two, andpeer.jswas still sitting staged in the index afterwards, untouched. - That second form is the one the skill teaches for a new, deleted or renamed path, which has to reach the index before a pathspec commit can see it.
Templates: four copyable blocks in the file, two of which the rule belongs in, both edited. โ
| Block | Subject | Needed the new text |
|---|---|---|
Wire-check bash block, "at the start of any task" | in-flight branches and PRs | Yes. Gained git status --short and a comment saying a bare commit would take whatever a peer has staged |
New bash block in the parallel-git section | staging and committing | Yes. It IS the rule: a bare-commit NO, a wider -A NO, an ALSO NO for files left staged, and two YES forms |
Kill-by-name bash block | killing processes by cwd, not pattern | No. A git staging rule inside it would fire on the wrong subject |
Indented pgrep cwd-walk block | verifying the box is quiet | No, same reason |
- Counted with
grep -c '^```'and a scan for indented blocks, not by reading the first one. After the correction,grep -c -- 'add -A'was run over the source AND all five generated copies to confirm no template still carried the wrong cause as the headline.
This lane's own commit e7b4acf4 used the shape the rule forbids. โ
- It ran
git add <7 exact paths> && git commit -F <msgfile>, a bare commit, and landed clean only because the index had been checked empty first and no peer committed in the window. That is verification and luck, not structure. - Recorded because it is the same gap the incident went through, found by writing the rule rather than by the commit failing.
The sync produced five generated copies, committed with the source. โ
npm run sync:skillswrote 162 files, 0 warnings, 0 errors, twice (once per version of the rule). Only themulti-agentset changed either time:.claude/skills/multi-agent/SKILL.md,.github/skills/multi-agent/SKILL.md,.gemini/skills/multi-agent/skill.md,.github/prompts/multi-agent.prompt.md,.gemini/commands/multi-agent.toml.- All five carry the identical insertion, so no copy forked from the source.
What was left open, and then closed? โ
Both follow-ups were handed back to this lane and are done, in 47862fe2. โ
docs/projects/brainstorm-archive/README.mdsaid the EXCLUDE entry "has not been applied. It is the operator's call". It now records that she made the call, that66822019applied it, and that the exemption stops atmockups/(verified by the control run, not asserted). Its section question changed from "why does this trip the em dash lint" to "why is this exempt"..agents/rules/project/linter-organization.mdlisted only the harness-capture exclusion. The second one is now beside it with its one-clause reason, its anchoring, and its commit. Its lead said "two things to know" over three sub-bullets, so the count came out rather than being incremented into staleness.
A third ratchet check went in beside DIRECTION and MEANING: a LOOSENING needs a CONTROL. โ
- Both existing checks are written for a ceiling moving DOWN, and neither asks whether the thing is still a gate. A green run after a loosening is ambiguous by construction, because "nothing violates it" and "nothing is scanned any more" print the same line.
- The control goes just OUTSIDE the new boundary, so one run proves the gate is alive AND that the exclusion did not overreach. The worked example in the rule is this task's own control run.
- DIRECTION gained a loosening half too, since under a lead that now covers both operations "every ceiling must move DOWN or stay" would read as a ban on loosening.
The router line was rewritten in the same edit. โ
- It carried the old two checks and a stale "two traps" count. A router that forks from the body is the only copy most agents read, so it is the template-drift shape in a different costume.
npm run sync:agentsregenerated AGENTS.md,.github/instructions/linter-organization.instructions.mdand.cursor/rules/linter-organization.mdc;sync:agents --checkreports 14 rules in sync.