Skip to content

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.md plus 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.md records 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 own README.md sits 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 .md under it.
  • No shipped UI lives there. isUiSourceFile() only classifies paths under apps/*/src/ or packages/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 folder mockups.
  • 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 validate for 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. โ€‹

RunResult
npm run lint:emdash before the changeexit 1, 27 files listed, all under brainstorm-archive/mockups/
npm run lint:emdash after the changeexit 0, 704 baseline files, 6938 grandfathered occurrences, 0 confirmed UI-copy hits
Control: one new em dash appended to docs/projects/brainstorm-archive/README.mdexit 1, README.md: 1 occurrence(s) (baseline allows 0)
After restoring the control fileexit 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 git command, 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 bare git commit takes 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 "never git 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 . and commit -a stay 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 commit e7b4acf4. The lane that caused it reported the real sequence afterwards.
  • Corrected in a follow-up commit rather than an amend, since e7b4acf4 was 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 bare git commit gets 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.js staged alongside my mine.js, a bare git commit -m ... produced a commit containing both.
  • The fix: git add new.js && git commit -m ... -- new.js mine.js produced a commit containing exactly those two, and peer.js was 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. โ€‹

BlockSubjectNeeded the new text
Wire-check bash block, "at the start of any task"in-flight branches and PRsYes. 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 sectionstaging and committingYes. 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 blockkilling processes by cwd, not patternNo. A git staging rule inside it would fire on the wrong subject
Indented pgrep cwd-walk blockverifying the box is quietNo, 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:skills wrote 162 files, 0 warnings, 0 errors, twice (once per version of the rule). Only the multi-agent set 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.md said the EXCLUDE entry "has not been applied. It is the operator's call". It now records that she made the call, that 66822019 applied it, and that the exemption stops at mockups/ (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.md listed 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:agents regenerated AGENTS.md, .github/instructions/linter-organization.instructions.md and .cursor/rules/linter-organization.mdc; sync:agents --check reports 14 rules in sync.

Built with VitePress