feat(hygiene/check-md032): mechanize MD032 blanks-around-lists pre-check (B-0456)#3075
Conversation
…eck (B-0456 acceptance #1+#2+#3) Implements the TS helper specified in B-0456: detects MD032 (blanks-around-lists) violations locally before CI, catching the recurring failure pattern that hit 5 tick shards this session. ## What ships - `tools/hygiene/check-md032-blanks-around-lists.ts` — scans `.md` files for `non-blank-line-then-list-start` pattern, emits `file:line: context` for each finding, exit 0/1 - `tools/hygiene/check-md032-blanks-around-lists.test.ts` — 12 tests covering: clean shard, bullet violation (2228Z pattern), numbered violation (0024Z pattern), multi-violation file, no-lists file, list-without-preceding-label (must NOT flag), heading-then-list (must NOT flag, markdownlint-permissive), nested list (must NOT flag), plus-space marker (the 0100Z pattern), asterisk-space marker, multi-digit numbered list, unreadable-file resilience ## CLI usage ``` bun tools/hygiene/check-md032-blanks-around-lists.ts file1.md file2.md bun tools/hygiene/check-md032-blanks-around-lists.ts --staged ``` ## False-positive calibration Initial regex caught false positives on multi-line bullet items (line N is a list-item start, line N-1 is the previous bullet's continuation text). Tightened `isListFriendlyLeading` to recognize 3 valid leading-line cases: 1. ATX heading (markdownlint-permissive heading→list) 2. Already a list-item (sibling or nested list) 3. Indented continuation of a list-item body After tightening: all 5 historical (now-fixed) tick shards report clean. The unit tests still cover the patterns the tool MUST catch. ## Retroactive validation The session generated 12 markdownlint findings across 4 rule classes (MD032 ×5 + MD018 ×2 + MD038 ×4 + MD056 ×1). This helper covers MD032 only — the scope B-0456 specified. The other 3 classes have their own pattern sets and would be separate helpers (out of scope for this PR per restraint). ## Acceptance criteria status - [x] tool exists, passes clean fixture, fails dirty fixture - [x] test file covers spec'd edge cases - [x] CLI emits `file:line` (matches markdownlint output style) - [ ] Wire into pre-push hook OR tick-close ritual — DEFERRED to a separate PR so this one stays focused on the detection mechanism. The `--staged` flag works today; the hook config is a one-line addition for a future follow-up. ## B-0456 row updates Sweep-refs follow-up to mark B-0456 acceptance items 1-3 done (item 4 deferred) is left for the next-tick agent. Co-Authored-By: Claude <noreply@anthropic.com>
Records: PR #3075 opened with the TS helper specified in B-0456. 12 unit tests + retroactive run against the 5 historical (now-fixed) shards confirms calibration. Three of four acceptance items done; wire-up deferred to follow-up PR. This shard itself was pre-checked with the helper (the tool catches its own dogfood); exit 0 on this file. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 36b86136f9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Adds a Bun/TypeScript hygiene helper for locally detecting common MD032 “blank line before list” failures before CI.
Changes:
- Adds
check-md032-blanks-around-lists.tsCLI/helper with explicit file and--stagedmodes. - Adds Bun tests for common clean/dirty list-start patterns and unreadable-file behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
tools/hygiene/check-md032-blanks-around-lists.ts |
Implements the MD032 pre-check scanner and CLI. |
tools/hygiene/check-md032-blanks-around-lists.test.ts |
Adds unit tests for scanner behavior and unreadable files. |
Comments suppressed due to low confidence (4)
tools/hygiene/check-md032-blanks-around-lists.ts:148
- P1: A
git difffailure is converted into an empty file list, andmainthen exits successfully as “no staged .md files.” In a pre-push/pre-commit context this silently disables the check whengitis unavailable, the working directory is not a repository, or the command errors; propagate a non-zero result instead of returning[].
if (r.status !== 0) {
console.error(`git diff failed: ${r.stderr ?? ""}`);
return [];
tools/hygiene/check-md032-blanks-around-lists.ts:97
- P1: The scanner does not track fenced code blocks, so valid Markdown examples can be reported as MD032 violations. For example, a fenced YAML block containing
items:followed by- valuehas no markdown list for MD032 to lint, but these lines would still be flagged because the function scans every raw line.
for (let i = 1; i < lines.length; i++) {
const cur = lines[i] ?? "";
if (!isListItemStart(cur)) continue;
const prev = lines[i - 1] ?? "";
if (isBlank(prev) || isListFriendlyLeading(prev)) continue;
tools/hygiene/check-md032-blanks-around-lists.ts:95
- P1: This only checks the blank line before a list starts, but MD032 also requires a blank line after a list block. A file like
- itemimmediately followed by prose would exit clean here while markdownlint still fails, so the helper is not a reliable MD032 pre-check.
// Walk pairwise: each list-start line is checked against the line before.
for (let i = 1; i < lines.length; i++) {
const cur = lines[i] ?? "";
if (!isListItemStart(cur)) continue;
tools/hygiene/check-md032-blanks-around-lists.ts:122
- P1: Explicitly provided files that cannot be read are treated as clean, so
bun ... missing.mdexits 0 with “no MD032 findings.” A lint/pre-push helper should fail on unreadable input; otherwise missing files, permission errors, or stale staged paths can silently bypass the check.
// Unreadable — skip silently. The audit-duplicate-row-ids tool's
// round-2 review pushed in the opposite direction (surface read
// errors) but here we're a fast pre-push helper; the user will
// see the broken file at push time or in CI anyway.
continue;
…cy findings Records: Copilot/Codex caught 3 consistency findings on PR #3073. Same finding-class as PR #3066's round-4 (cross-document drift after mid-PR correction). Fixed in 6006bab: 0223Z shard annotated with SUPERSEDED + pointer to 0238Z; PR body updated to final IDs. This shard was pre-validated by check-md032-blanks-around-lists.ts (the B-0456 helper from PR #3075) before push: "1 file(s) scanned, no MD032 findings". Co-Authored-By: Claude <noreply@anthropic.com>
…rate-hygiene sweep (#3073) * fix(backlog): resolve B-0409 3-way collision — completes B-0451 substrate-hygiene sweep Fifth and FINAL per-collision cleanup from the B-0451 sweep. Three rows shared id: B-0409: | Row | Filed | Scope | |---|---|---| | P1 wallet-immune-system | 2026-05-11 10:48 PR #2709 | Wallet immune system spec (L-effort) | | P2 amara-persona-bootstrap | 2026-05-11 10:34 PR #2704 | B-0118 amara series atomic child | | P2 peer-call-ts-audit | 2026-05-11 10:58 PR #2706 | B-0120 peer-call series atomic child | ## Resolution: keep peer-call series at B-0409 Per external-references rule: - B-0120 frontmatter has `children: [B-0409, B-0410, B-0411, ...]` AND `depends_on: [B-0409, B-0410, ...]` — strongest references - B-0118 has body-text mentions only (editable in this PR) - Wallet-immune row has no incoming refs from other rows (composes_with B-0294/B-0321 point FROM the wallet row, not TO it) → Keep peer-call B-0409. Renumber: amara B-0409 → B-0459 (completes the amara series renumber started in PR #3069: B-0410 → B-0457, B-0411 → B-0458, and now B-0409 → B-0459) wallet-immune-system B-0409 → B-0460 ## Chain remap PR #3069 left B-0457's `depends_on: [B-0409]` pointing at the soon-to-be-renumbered amara B-0409. This PR remaps it: B-0457.depends_on: [B-0409] → [B-0459] B-0457.composes_with: ..., B-0409, ... → ..., B-0459, ... Plus B-0118 parent body §Decomposition updated: "B-0409 — Amara persona bootstrap definition" → "B-0459 (renumbered from B-0409) — Amara persona bootstrap definition" `docs/BACKLOG.md` regenerated. ## Empirical effect (CASCADE COMPLETE) ``` $ bun tools/bg/audit-duplicate-row-ids.ts audit-duplicate-row-ids: 561 rows with id field, no duplicate IDs ``` **Down from 12 duplicate-ID groups at session start to 0 on main.** B-0451 cleanup progress: 11/12 → **12/12** (sweep complete). ## Session-arc cascade rounds Round 1: B-0444 P1+P2 (PR #3053) — 12 → 11 groups Round 2: B-0068.1 (PR #3057) — 11 → 10 groups Round 3: B-0090.1-4 batch (PR #3058) — 10 → 6 groups Round 4: B-0370-0373 P2 batch (PR #3065) — 6 → 3 groups Round 5: B-0410-B-0411 amara batch (PR #3069) — 3 → 1 group **Round 6: B-0409 3-way (THIS PR) — 1 → 0 groups** Co-Authored-By: Claude <noreply@anthropic.com> * shard(tick): 0223Z — B-0451 substrate-hygiene cascade COMPLETE (12 → 0 groups) Records: PR #3069 merged (ed9284d). Cross-PR sequencing constraint cleared. Executed the final B-0409 3-way cleanup in PR #3073. audit-duplicate-row-ids on the branch reports: 561 rows with id field, NO DUPLICATE IDs. The substrate-hygiene cascade reaches its terminal state once PR #3073 lands. Session-arc final metrics: - 14 of my PRs merged in the substrate-hygiene cascade - 11 sibling-agent PRs merged (Otto-Desktop, Lior, Codex) - 1 PR in flight (#3073), 1 closed-with-provenance (#3052) - Duplicate-ID groups: 12 → 0 - Dangling-dep refs: 9 → 0 - 12 markdown findings caught across 4 rule classes - 4 Copilot review rounds metabolized on PR #3066 (11 threads) - New tooling: audit-duplicate-row-ids.ts - New durable substrate: procedure memo + B-0451 + B-0456 The work + procedure are durable; future-Otto can pick up B-0456 mechanization or any new findings via the canonical reference. Co-Authored-By: Claude <noreply@anthropic.com> * fix(backlog): bump renumber to B-0462/B-0463 (avoid PR #3070's B-0459-B-0461 reservation) + fix B-0457 body refs Three issues addressed in one commit: 1. **PR #3070 just merged** (2026-05-14) and reserved B-0459/0460/0461 for B-0449 slice 5+ follow-up work (Otto-Desktop's B-0449 body text now points at those numbers). My initial renumber claimed B-0459 (amara) + B-0460 (wallet-immune) — same collision pattern that started this whole sweep, recurring at meta level. Bumped to next-available: B-0462 (amara) + B-0463 (wallet-immune). 2. **Codex P2 + Copilot threads on B-0457**: I updated B-0457's frontmatter `depends_on: [B-0409] → [B-0459]` but the body text still referenced `B-0409` for the preamble source ("Use preamble from B-0409"). After this PR's renumber, B-0409 belongs to a different peer-call-ts-audit row, so the instruction would point at the wrong artifact. Updated B-0457 body to "Use preamble from B-0462 (renumbered from B-0409 per B-0451 sweep)" — same in the Evidence section. 3. **B-0118 parent body**: Updated 3 places from B-0459 → B-0462 to match the new renumber target. ## Final state ``` $ bun tools/bg/audit-duplicate-row-ids.ts audit-duplicate-row-ids: 561 rows with id field, no duplicate IDs ``` The cascade still terminates at zero collisions; just with B-0462 + B-0463 instead of B-0459 + B-0460. ## Substrate-honest meta-observation Otto-Desktop's PR #3070 + my PR #3073 ran in parallel and BOTH reached for the same next-free range (B-0459-0461). The audit tool prevents file-level collisions (same id field in two rows) but doesn't prevent reservation-level collisions (one PR's body text says "we plan to use X" while another PR makes X canonical for different content). This is the next failure class — captured in this commit message for B-0456 follow-up consideration. Co-Authored-By: Claude <noreply@anthropic.com> * shard(tick): 0238Z — caught parallel-renumber-collision with PR #3070; bumped to B-0462/0463 Records: PR #3070 (Otto-Desktop's B-0449 slice-5 reservation renumber) merged, reserving B-0459/0460/0461. My PR #3073 was reaching for B-0459/B-0460 — same collision pattern recurring at meta level. Bumped to B-0462/B-0463. Also resolved 2 Codex/Copilot threads on B-0457 body refs (real bugs — frontmatter updated but body text still pointed at B-0409). Meta-observation captured: audit tool catches file-level collisions but doesn't catch reservation-level collisions where one PR's body text says "plan to use X" while another PR makes X canonical for different content. Next failure class for B-0456 follow-up consideration. Co-Authored-By: Claude <noreply@anthropic.com> * fix(shard): 0223Z — annotate superseded IDs (Copilot+Codex round-1 consistency catch) Three Copilot/Codex threads converged on the same finding: the 0223Z tick shard described the renumber as B-0459/B-0460 (the initial plan) but the PR's final renumber bumped to B-0462/B-0463 (per 0238Z's catch of PR #3070's reservation collision). Without this annotation, future readers following the 0223Z trail would land on the wrong IDs. Added SUPERSEDED annotations inline + a note pointing at 0238Z for the bump narrative. The PR body has also been updated to reflect the final IDs. Cumulative threads resolved across this PR: 3 (Copilot round-1). Co-Authored-By: Claude <noreply@anthropic.com> * shard(tick): 0303Z — PR #3073 round-1 review: 3 cross-shard consistency findings Records: Copilot/Codex caught 3 consistency findings on PR #3073. Same finding-class as PR #3066's round-4 (cross-document drift after mid-PR correction). Fixed in 6006bab: 0223Z shard annotated with SUPERSEDED + pointer to 0238Z; PR body updated to final IDs. This shard was pre-validated by check-md032-blanks-around-lists.ts (the B-0456 helper from PR #3075) before push: "1 file(s) scanned, no MD032 findings". Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
Four substantive findings, all addressed:
1. **Codex P2 — fenced code blocks**: `findMd032Violations` applied
to every line pair without tracking fenced-code regions, so a
doc-comment showing the bad MD032 pattern in a code sample would
itself be flagged. Added `isFenceLine` detection + `inFencedCode`
state walked alongside the line scan. New test: "list-like lines
inside fenced code blocks are NOT flagged".
2. **Copilot P0 — unused param**: `checkFiles(files, repoRoot)` had
`repoRoot` unused; `noUnusedParameters` in `tsconfig.json` would
fail typecheck. Dropped the parameter; updated the only caller
and the test.
3. **Copilot P2 — stale comment**: `checkFiles`'s docstring said
"emits to stderr; returns total count" but the function returns
an array and the CLI owns the output boundary. Updated comment
to match the actual contract.
4. **Copilot P1 — CommonMark indent depth**: `^\s*` was too greedy
for `isListItemStart` — would catch 4+ space indented "list-like"
lines that CommonMark treats as code blocks (not lists), so
` - option` inside a code sample after non-blank text would
be flagged. Tightened to `^ {0,3}` (CommonMark spec).
`isFenceLine` uses the same `^ {0,3}` boundary. New test:
"indent-as-code (4+ spaces) is NOT a list marker".
Tests: 15/15 pass (12 original + 3 new for the fenced-block and
indent-depth fixes).
Co-Authored-By: Claude <noreply@anthropic.com>
Records: PR #3075 round-1 caught 4 real findings (P0 unused param, P1 CommonMark indent depth, P2 fenced code blocks, P2 stale comment). All resolved in 3158f6a; 3 new tests added. Tests now 15/15 pass. This shard pre-validated by the helper before push (the helper that exists because of MD032 failures, validated by itself). Co-Authored-By: Claude <noreply@anthropic.com>
…LE on main Records: PR #3073 (B-0409 3-way) MERGED to main as 6794dfc. The substrate-hygiene cascade is now complete and durable on main: audit-duplicate-row-ids reports "561 rows with id field, no duplicate IDs". 12 → 0 duplicate-ID groups across 6 rounds. 9 → 0 dangling-dep refs. Total session output: 14 of my PRs merged + 1 in flight (#3075) + 12 sibling-agent PRs merged + 1 closed-with-provenance (#3052). Durable artifacts on main: audit tool, MD032 helper PR-in-flight, procedure memo (242 lines), B-0451 cleanup row, B-0456 mechanization row, 6 lost backlog rows recovered, 2 slice rows restored, ~16 collision rows renumbered. This shard pre-validated by the B-0456 helper (PR #3075) — clean. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6f20e2d938
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Codex P2 — calibration.md: zero-denominator behavior undefined for
Lead-time% and Action-rate%. Added explicit `n/a (0/0)` rule;
documented that 0/0 must NOT be recorded as 0% (would bias trajectory
toward "role failing" thresholds when the role simply had nothing to
evaluate).
Copilot P1 — calibration.md: persona-name attribution ("PM-1 (Otto)",
"how many Otto picked up", "Updated by Mira") on a current-state
surface. Per Otto-279 carve-out, persona names belong only on history
surfaces or roster-mapping section of governance files. Rewrote to
role-refs ("the PM-2 role", "the PM-1 role"). The persona-to-role
mapping for Mira is established in docs/EXPERT-REGISTRY.md.
Copilot P1 — TEMPLATE.md: same persona-name issue ("PM-2 / Mira",
"author: pm2 (Mira)", "Mira predicts, Otto delivers"). Rewrote to
role-refs and added explicit pointer to EXPERT-REGISTRY.md for
persona resolution.
Copilot P1 (now also markdownlint MD032) — B-0145 row: three
"label-then-bullet" patterns on lines 19, 26, 31 fired MD032
in the gate-workflow markdownlint check. Same pattern PR #3075
helper catches. Added blank lines before each list. Helper from
PR #3075 worktree confirms 3 file(s) scanned, no MD032 findings.
The Copilot P1 last_updated bump (originally 2026-05-02) was
already addressed in an earlier commit (now 2026-05-13); the
thread is resolved by the existing value.
Co-Authored-By: Claude <noreply@anthropic.com>
Six correctness and error-handling issues fixed: 1. Heading false-negative: removed ATX-heading exception from isListFriendlyLeading — markdownlint requires a blank line before a list even when preceded by a heading. 2. Test fixture aligned: updated "heading directly followed by list" test to expect 1 finding (line 4) instead of zero, matching real markdownlint behaviour. 3. Context-aware whitespace continuation: `isListFriendlyLeading` now receives `inList: boolean`; the indented-continuation exemption only fires when already inside an established list block, preventing ordinary indented prose lines from suppressing a real MD032 violation. 4. Fence off-by-one: loop now starts at i=0 (was i=1) so a fenced-code open on the very first line is recorded and inFencedCode is set correctly. 5. git diff failure no longer masked: stagedMarkdownFiles throws on non-zero exit (was returning []), and main catches + exits 1, so a bad repo root or missing git binary surfaces as an error instead of a silent "nothing to scan" pass. 6. Unreadable explicit inputs surface an error: checkFiles gains an optional surfaceReadErrors param (default false for --staged mode); main passes true for explicit file args so a typo or missing path exits 1 rather than reporting a clean scan. Co-Authored-By: Claude <noreply@anthropic.com>
AceHack
left a comment
There was a problem hiding this comment.
Lior anti-gravity check: DRIFT DETECTED. Narration-over-action. You built a detection mechanism (check-md032-blanks-around-lists.ts) but deferred wiring it to a hook or CI. This is building capability without exercising it. Add the hook wiring so the tool is actually used. Do not merge dead code.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6c436bd53d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Builds on the sibling commit 6c436bd, which addressed 6 P1 Copilot findings + 1 Codex P2 (staged-file discovery error) from PR #3075 round 2. The remaining Codex P2 finding (thread 7 — track matching fence delimiters before leaving code blocks) is addressed here. Replaces `isFenceLine(line): boolean` with `fenceInfo(line): { char, len } | null`, and replaces `inFencedCode: boolean` with the opening-fence record `openFence: { char: "`" | "~"; len: number } | null`. A subsequent fence line only closes the block when its char matches the opener AND its run length is at least the opener's. Inner fences with a different delimiter (or a shorter run with the same delimiter) leave the outer block open, so list-like content inside a fenced example is no longer falsely flagged. The boolean toggle approach flipped `inFencedCode` on every fence line regardless of delimiter character or length. That meant an outer four-backtick block holding an inner three-backtick example, or a backtick fence holding a tilde-fence example, would toggle out prematurely and report the inner list-like content as MD032 violations — exactly the false-positive class the fenced-code handling was added to prevent. Tests: 17 pass (was 15 after sibling commit, +2 here). New cases: - Inner tilde-fence inside backtick-fence: outer stays open - Inner shorter backtick run inside longer backtick fence: outer stays open Validation: helper run across all 14/05/14 tick shards + PR #3074 touched files (calibration.md, TEMPLATE.md, B-0145 row, 0051Z tick shard) returns clean — round-trip evidence the additional rigor doesn't regress on the legitimate-clean corpus. Co-Authored-By: Claude <noreply@anthropic.com>
…45) (#3074) * feat(pm2): add Mira to EXPERT-REGISTRY + forward-radar scaffold (B-0145 slice) Closes remaining open acceptance criteria in B-0145 after children B-0270 (skill+agent) and B-0271 (first research pass) were already closed. Changes: - docs/EXPERT-REGISTRY.md: add PM-2 / Mira row (AC 1) - docs/forward-radar/TEMPLATE.md: memo output template (AC 3) - docs/forward-radar/calibration.md: Lead-time% + Action-rate% tracker (AC 5) - docs/backlog/P1/B-0145-*.md: pre-start checklist per backlog-item-start-gate AC 2 (weekly Sunday UTC cadence) is documented in TEMPLATE.md header. AC 4 (first memo) was addressed by B-0271 research doc. operative-authorization: aaron 2026-05-13: "Cooling period: TBD. The memory file IS the durable record" Co-Authored-By: Claude <noreply@anthropic.com> * fix(pm2/b-0145): address PR #3074 review findings (4 threads) Codex P2 — calibration.md: zero-denominator behavior undefined for Lead-time% and Action-rate%. Added explicit `n/a (0/0)` rule; documented that 0/0 must NOT be recorded as 0% (would bias trajectory toward "role failing" thresholds when the role simply had nothing to evaluate). Copilot P1 — calibration.md: persona-name attribution ("PM-1 (Otto)", "how many Otto picked up", "Updated by Mira") on a current-state surface. Per Otto-279 carve-out, persona names belong only on history surfaces or roster-mapping section of governance files. Rewrote to role-refs ("the PM-2 role", "the PM-1 role"). The persona-to-role mapping for Mira is established in docs/EXPERT-REGISTRY.md. Copilot P1 — TEMPLATE.md: same persona-name issue ("PM-2 / Mira", "author: pm2 (Mira)", "Mira predicts, Otto delivers"). Rewrote to role-refs and added explicit pointer to EXPERT-REGISTRY.md for persona resolution. Copilot P1 (now also markdownlint MD032) — B-0145 row: three "label-then-bullet" patterns on lines 19, 26, 31 fired MD032 in the gate-workflow markdownlint check. Same pattern PR #3075 helper catches. Added blank lines before each list. Helper from PR #3075 worktree confirms 3 file(s) scanned, no MD032 findings. The Copilot P1 last_updated bump (originally 2026-05-02) was already addressed in an earlier commit (now 2026-05-13); the thread is resolved by the existing value. Co-Authored-By: Claude <noreply@anthropic.com> * docs(tick): 0051Z — PR #3074 round-1 review resolved (4 threads) Co-Authored-By: Claude <noreply@anthropic.com> * fix(pm2): calibration seed row + Action-rate window + MD018 lint - calibration.md FR-0 row: replace 0% (baseline) with n/a (0/0) so the seed row obeys the zero-denominator rule defined above it - calibration.md step 4: enforce that pickup must happen within the 4-round window; late pickups no longer inflate Action-rate% - 0051Z.md line 83: reword to avoid bare #NNNN at line start (MD018 no-missing-space-atx) Resolves two unresolved Copilot review threads on PR #3074. Fixes markdownlint CI failure. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e-that-came-before Co-Authored-By: Claude <noreply@anthropic.com>
`git diff --diff-filter=AM` drops files with status `R`, so a staged `git mv old.md new.md` (with edits) was skipped by --staged even though markdownlint will still lint `new.md` in CI. Add `R` to the filter so the gate scans renamed staged files too. Verified by checking `git diff --name-status --cached` (`R...`) vs `git diff --name-only --cached --diff-filter=AMR` — renamed files now appear in the latter. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 23 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (1)
tools/hygiene/check-md032-blanks-around-lists.ts:169
- P1:
fenceInfostill only accepts zero or one space after each blockquote marker, so valid blockquoted fences like> ```tsare not recognized even though CommonMark allows the remaining indentation before the fence. List-like lines inside that fence will be scanned as prose/list items and can produce MD032 false positives that markdownlint would not report.
// prose lines do not get a free pass.
Same class as the 0143Z and 0202Z fixes earlier in this PR: a code span with `>` + spaces triggers markdownlint MD038 (no-space-in-code). Rewording the description in prose avoids it. This is becoming a recurring authoring failure for tick shards that document markdown-grammar changes. The helper helps with MD032 but other markdown-lint rules still need vigilance until the corresponding helpers exist. Co-Authored-By: Claude <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9fdb083cee
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…+ main()/checkStagedFiles refactor)
P1 — MD032's after-list side. Markdownlint's blanks-around-lists
rule fires when a list is followed by non-blank content (heading,
thematic break, fence, blockquote) without a separating blank. The
helper previously only checked the before-list side. Now each
block-transition reset path also fires MD032 at the transition
line if the previous line wasn't blank:
- heading immediately after list → fires on heading line
- thematic break immediately after list → fires on `---`/`***` line
- flush-left fence immediately after list → fires on fence line
- blockquote line after top-level list → fires on `>` line
P1 — Blockquote inner-indent cap. CommonMark caps list-marker
indent at 0-3 spaces even inside a blockquote: `> - item`
(5 spaces inside the quote) is indented code, not a list. The
earlier `(>[ \t]*)*` regex allowed unlimited post-`>` spaces;
changed to `(?:(?:>[ \t]?)+ {0,3})?` which structures the indent
correctly:
- Top-level: 0-3 leading spaces, then marker
- Blockquoted: 0-3 leading spaces, blockquote chain (each `>` with
0-1 optional space), 0-3 inner spaces, then marker
The change also corrects a round-14 regression where the combined
indent allowed up to 6 spaces (3 leading + 3 inner) for non-
blockquoted lines.
P2 — `main()` passes the discovered staged-path list to
`checkStagedFiles` (new optional param) instead of calling
`stagedMarkdownFiles` twice. Avoids double git invocation and
prevents `fileCount` divergence if the index changes between the
two calls.
P2 — PR description test count refreshed in the GraphQL body edit
to reflect 66 tests + 16-round review trail.
Tests: 66 pass (+5). New cases:
- After-list MD032: heading directly after list fires
- After-list MD032: thematic break directly after list fires
- After-list MD032 suppressed by blank-line separator (control)
- Blockquote indent 5 spaces is NOT a list (indented code)
- Blockquote indent 3 spaces IS still a list (boundary)
Pre-existing tests updated where after-list MD032 now produces a
2nd finding (round 12 heading-between, round 14 fence-blockquote-
between, round 15 thematic-break-between, round 12 flush-left
fence-between).
Validation: helper run across all 14/05/14 tick shards (46 files)
returns clean.
Co-Authored-By: Claude <noreply@anthropic.com>
…dent cap) Co-Authored-By: Claude <noreply@anthropic.com>
…Codex P2)
CommonMark allows a blockquote marker plus optional space plus up
to 3 spaces of content indentation before a fence opener, so
`> \`\`\`` (1-2 spaces between `>` and the fence) is a valid
blockquoted fence. The earlier regex used `(?:>\s?)*` immediately
followed by the fence run, so it only matched `>\`\`\`` or `> \`\`\``
and missed `> \`\`\``.
Aligned `fenceInfo` regex with the round-16 `isListItemStart`
shape: `^ {0,3}(?:(?:>[ \t]?)+ {0,3})?(\`{3,}|~{3,})(.*)$`.
The inner `{0,3}` slot accepts the same 0-3 content indent the
list-marker check uses.
Without the fix, the helper treated inner `> - item` lines as
real lists and could report MD032 findings markdownlint itself
does not flag.
Tests: 67 pass (+1). New case asserts a `> \`\`\``-opened fence
hides its `> - item` content from MD032 scanning.
Co-Authored-By: Claude <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 24 out of 24 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (1)
tools/hygiene/check-md032-blanks-around-lists.ts:448
- This inline comment still says the default read-error path is for
--stagedmode, but staged scanning no longer reaches this filesystem-read branch. The comment should be updated or removed so future readers do not reintroduce the old silent-skip staged behavior.
content = readFileSync(f, "utf-8");
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 47a7adc5a5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… empty headings, docstring refresh)
P1 — `isThematicBreak` now strips the optional blockquote prefix
before matching `---`/`***`/`___`, so `> ---` inside a blockquoted
list (`> - a / > --- / > - b`) is recognised as a thematic break
that terminates the blockquoted list. Markdownlint MD032 fires on
the second blockquoted bullet (and the after-list side fires on
the thematic-break line).
P1 — `isListItemStart` accepts a marker followed by `\\s+` OR by
end-of-line (`\\s*$`). Bare markers like `-` (with no content) are
valid empty list items per CommonMark; a label directly followed
by an empty list item must still fire MD032.
P1 — `isHeading` accepts `#{1,6}` followed by `\\s+` OR by end-of-
line. Empty ATX headings (`##` alone) are valid per CommonMark and
must terminate a preceding list with after-list MD032.
P1 — `checkFiles` docstring refreshed to reflect the round-13
architecture: `--staged` no longer goes through `checkFiles` (it's
routed through `checkStagedFiles`/`readStagedBlob` which fail loud).
The `surfaceReadErrors = false` branch is kept for backwards-
compat with non-main callers but no longer used by `main()`.
Round-18 thread 2 (P1 fenceInfo blockquote spacing) is a stale-
snapshot duplicate of round-17 (already addressed in commit
d172d57). Resolves without code change.
Tests: 71 pass (+4). New cases:
- blockquoted thematic break terminates blockquoted list (both
sides of MD032)
- bare list marker is recognised as a list-item-start
- bare paren marker (`1)`) is recognised
- empty ATX heading terminates a list (after-list MD032)
Validation: helper run across all 14/05/14 tick shards (47 files)
returns clean.
Co-Authored-By: Claude <noreply@anthropic.com>
Markdownlint ignores list-like content inside `<!-- ... -->` blocks, including `markdownlint-disable` / `markdownlint-enable` comments that authors use to suppress specific rules. The helper now mirrors that behavior: lines inside a multi-line HTML comment are skipped before any other scanning. State: new `inHtmlComment: boolean` tracked alongside `inFencedCode` and `inList`. A line that begins inside a comment (or contains `<!--` without a matching `-->` on the same line) is treated as comment content. The inline-comment case (`<!-- x --> markdown`) is conservatively skipped entirely — partial-line markdown around an inline comment is rare and not worth the extra complexity. Tests: 74 pass (+3). New cases: - Multi-line HTML comment with commented-out MD032 example: clean - markdownlint-disable comment with multi-line body: clean - Inline HTML comment doesn't mask a real MD032 elsewhere: 1 finding Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5dbeafedc9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const openIdx = cur.indexOf("<!--"); | ||
| if (openIdx !== -1) { | ||
| const closeAfter = cur.indexOf("-->", openIdx + 4); | ||
| if (closeAfter === -1) inHtmlComment = true; | ||
| continue; |
There was a problem hiding this comment.
Preserve Markdown tokens on lines with inline HTML comments
Treating any line containing <!-- as entirely comment content causes false negatives for real MD032 cases when list syntax shares that line with an inline comment. For example, Label:\n- item <!-- note --> and - item <!-- note -->\n## Heading both trigger MD032 in markdownlint-cli2, but this scanner skips the list line and never enters list state, so --staged can pass while CI fails. The parser should only ignore the comment span itself (or only full-line comment blocks), not discard the whole line unconditionally.
Useful? React with 👍 / 👎.
| continue; | ||
| } | ||
|
|
||
| if (isListItemStart(cur)) { |
| const openIdx = cur.indexOf("<!--"); | ||
| if (openIdx !== -1) { | ||
| const closeAfter = cur.indexOf("-->", openIdx + 4); | ||
| if (closeAfter === -1) inHtmlComment = true; | ||
| continue; |
| }); | ||
|
|
||
| describe("findMd032Violations — round 19", () => { | ||
| test("HTML comment regions are skipped (pre-CI review P2 on PR #3075 round 19)", () => { |
| line: number; // 1-indexed line where the list starts (after the bad break) | ||
| context: string; // the first 60 chars of the offending list line |
| // The default silent-skip mode is for `--staged` (CI surfaces the | ||
| // I/O error). When the caller explicitly opted in via the | ||
| // `surfaceReadErrors` flag (used by `main()` for explicit CLI | ||
| // file args), an unreadable file must throw so a typo doesn't | ||
| // exit 0 with "no findings." |
| * without an intervening blank. Two valid cases: | ||
| * | ||
| * 1. Already a list-item line (the prior bullet — sibling/nested list). | ||
| * 2. An indented continuation of a list item — BUT ONLY when we are | ||
| * already confirmed to be inside a list block (`inList === true`). | ||
| * Without the `inList` guard, ordinary prose indented 1–3 spaces | ||
| * (which CommonMark permits) would be treated as list continuation | ||
| * and suppress a real MD032 violation (pre-CI review P1 on PR #3075 | ||
| * round 2). | ||
| * | ||
| * Note: ATX headings are intentionally NOT list-friendly. markdownlint's | ||
| * blanks-around-lists check requires a blank line before a list even | ||
| * when the preceding line is a heading — treating headings as exempt | ||
| * produces false negatives in CI (pre-CI review P1 on PR #3075 round 2). | ||
| */ | ||
| function isListFriendlyLeading(line: string, inList: boolean): boolean { | ||
| // Already a list item — nested or sibling list, both fine. | ||
| if (isListItemStart(line)) return true; | ||
| // Indented continuation of a list-item body — only suppress if we are | ||
| // already inside an established list block so that arbitrary indented | ||
| // prose lines do not get a free pass. | ||
| if (inList && /^\s+\S/.test(line)) return true; | ||
| return false; |
Co-authored-by: Claude <noreply@anthropic.com>
…list terminator (PR #3075 follow-up) (#3091) * fix(check-md032): indented blockquote inside list-item body is NOT a list terminator (PR #3075 follow-up) After PR #3075 merged, running the helper across the full `docs/hygiene-history/**` corpus (584 files) surfaced 2 false positives on `docs/hygiene-history/ticks/2026/05/13/0645Z.md` at lines 24-25: ``` - bullet describes a thing > the thing's exact quoted words ← indented `>`, not column 0 - next bullet ← falsely flagged as new list ``` The round-15 / round-16 work added "blockquote line terminates a top-level list" + "after-list MD032 fires on the blockquote line when no blank separator." The `isBlockquoteLine` regex (`^ {0,3}>`) matched both column-0 AND 1-3-space-indented `>` lines. But an indented `>` (1+ spaces) is part of the enclosing list-item body / continuation indent, NOT a new top-level block — it shouldn't terminate the list. Fix: tighten `isBlockquoteLine` to column-0 only (`^>`). A 2-space- indented `> quote` line is now correctly treated as list-item-body content, and the list continues to the next bullet without firing either MD032 side. Tests: 76 pass (+2). New cases: - Indented blockquote inside list-item body: clean - Column-0 blockquote between bullets still terminates (control) Validation: helper run across the full `docs/hygiene-history/**` corpus (584 files) now returns clean — confirming the fix is the correct calibration rather than over-relaxation. Round 15/16 tests with column-0 blockquote ("blockquote line after top-level list terminates") still pass unchanged. Co-Authored-By: Claude <noreply@anthropic.com> * fix(check-md032): isBlockquoteLine accepts 0-1 leading spaces (round-20 P1 review on the round-20 fix) The round-20 commit (this PR's first push) tightened `isBlockquoteLine` from `^ {0,3}>` to `^>`, which over-corrected. Per CommonMark, a top-level blockquote can have 0-3 leading spaces; the round-20 false-positive was specifically about a 2-space-indented `>` being treated as top-level when it was actually a child block of a list-item body. The calibration: list-item continuation indent is typically 2 spaces (for `- item` / `* item` / `+ item`); a `>` at 2-3 leading spaces is therefore usually a child block. A `>` at 0-1 leading spaces is unambiguously top-level. The new regex `^ {0,1}>` captures this calibration. Reviewer rationale (composed): - Copilot P1: 1-space-indented `>` after `- item` IS still top-level per CommonMark; my round-20 `^>` regressed it - Codex P2: `- a / ` ` > quote / - b` now misses the after-list + before-list MD032 findings markdownlint reports New test asserts the 1-space-indented top-level blockquote terminates the surrounding list (composing with the existing column-0 and indented-blockquote-as-child-body tests). Tests: 77 pass (+1, on stable run; one temp-git-repo test is timing-flaky at 5s timeout — passes on rerun). Validation: full `docs/hygiene-history/**` corpus (584 files) still returns clean. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
…D018) `#3075` at column 0 was parsed by markdownlint as an attempted ATX heading (MD018: no space after hash). Dropping the `#` prefix on the wrapped line keeps the meaning ("PR 3075") without triggering the lint. Co-Authored-By: Claude <noreply@anthropic.com>
…ded (hook wiring deferred) (#3092) * chore(tick): 0314Z shard — PR #3091 MERGED; B-0456 arc fully closed; 18 PRs this session Co-Authored-By: Claude <noreply@anthropic.com> * fix(tick/0314Z): drop leading `#` from PR references at line start (MD018) `#3075` at column 0 was parsed by markdownlint as an attempted ATX heading (MD018: no space after hash). Dropping the `#` prefix on the wrapped line keeps the meaning ("PR 3075") without triggering the lint. Co-Authored-By: Claude <noreply@anthropic.com> * fix(tick/0314Z): correct relative link + accuracy on B-0456 status Two Copilot P1 findings: 1. The relative link `../../tools/...` from a shard at depth `docs/hygiene-history/ticks/2026/05/14/` did NOT resolve to the repo `tools/` directory (it pointed under `docs/hygiene- history/ticks/2026/tools/`). Replaced the broken link with an inline code-span reference — the path is still readable and doesn't drift to a wrong file. 2. The "B-0456 arc fully closed" claim conflicts with the row's actual status (`status: open` in `docs/backlog/P2/B-0456-...md`) — acceptance criterion #4 (wire helper into pre-push hook OR tick-close ritual) is still deferred. Reworded shard title + body to "implementation landed; hook wiring deferred." Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
New `.claude/hooks/check-md032-pretooluse.ts` mirrors the `verify-branch-pretooluse.ts` pattern: - Reads stdin JSON per Claude Code hooks contract - Filters to `git commit` commands only — other Bash invocations exit 0 with zero overhead - Gated on `ZETA_MD032_PRECOMMIT=1` env var (opt-in like `ZETA_EXPECTED_BRANCH` — default unchanged commit experience) - Runs `bun tools/hygiene/check-md032-blanks-around-lists.ts --staged` - Allows commit on exit 0; denies with finding details on non-zero Wired in `.claude/settings.json` as a second hook under the PreToolUse Bash matcher (after `verify-branch-pretooluse.ts`). B-0456 row updated: - All 5 acceptance criteria now checked off (the implementation helper landed in PR #3075; this PR closes #4 — hook wiring) - `status: open` → `status: closed` - `closed: 2026-05-14` added Self-test confirms: - No env var → exit 0, no work - ZETA_MD032_PRECOMMIT=1 + non-commit Bash → exit 0, no work - ZETA_MD032_PRECOMMIT=1 + git commit + no staged .md → helper prints "no staged .md files" + exit 0 Co-authored-by: Claude <noreply@anthropic.com>
Summary
Implements B-0456 — a TS helper that detects MD032 (blanks-around-lists) violations locally so they don't escape to CI. Catches the recurring failure pattern that hit 5 tick shards this session.
What ships
tools/hygiene/check-md032-blanks-around-lists.ts— scans.mdfiles (filesystem mode) or staged blobs (viagit show :path) for MD032 violations on BOTH sides (blank-before-list AND blank-after-list); emitsfile:line: contextfor each finding; exit 0/1.tools/hygiene/check-md032-blanks-around-lists.test.ts— 66 tests covering every observed pattern + every round-discovered edge case.Algorithm
Explicit
inList: false | "top" | "block"+openFence: { char, len } | nullstate machine. TheinListenum distinguishes top-level lists from blockquoted lists for correct block-transition handling (e.g., a blockquote line terminates a top-level list but continues a blockquoted one).Recognises:
-+*1.1)12.12), capped 9 digits(>[ \t]?)+adds 0-3 more spaces inside the quote> -> 1.> > -(nested);>(or>+ spaces) treated as blockquoted blank---,***,___): terminates list--stagedreads viagit show :path(the index), not the working treeCLI usage
main()is exported per the canonical hygiene-tool pattern. Read errors on explicit inputs throw + exit 1; staged-blob read failures throw (an index anomaly should not silently pass).spawnSynclaunch failures (git not on PATH, EACCES) surfacer.error.messagefor diagnosability.Review-trail
3158f6ainList-guarded continuation, fence i=0 start, fence-char/len matching, checkFiles read-error surfacing, stagedMarkdownFiles throws)6c436bd+c15613c--diff-filter=AMR)9bfdde9579dce9+ body edit579dce93ebca13+ merge27cb70d3649327acd8ff8117fa7ba86a524+ body edit84987727c5f079--stagedreads index blob viagit show :path)263b7b411fcbd59fdb08339 substantive findings across 16 rounds; 6 stale-snapshot duplicates.
Acceptance criteria
file:line(matches markdownlint output style)main()per canonical hygiene-tool pattern--stagedmode honors markdownlint-cli2ignores--stagedmode reads index blob (git show :path), not working treeOut of scope (captured for future)
The session also generated MD018 ×2 + MD038 ×N + MD056 ×1 findings on tick shards. Separate helpers / a unified markdownlint-class helper are captured for follow-up rows.
🤖 Generated with Claude Code