Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -679,5 +679,6 @@ are closed (status: closed in frontmatter)._
- [ ] **[B-0536](backlog/P3/B-0536-orphan-ferry-ref-cleanup-and-audit-false-positives-2026-05-15.md)** Orphan-ferry-ref cleanup + audit false-positive on filename paths
- [ ] **[B-0537](backlog/P3/B-0537-memory-md-index-entry-lengths-cleanup-and-gate-2026-05-15.md)** memory/MEMORY.md index-entry length cleanup (100 long entries) + CI gate
- [ ] **[B-0553](backlog/P3/B-0553-audit-backlog-status-drift-detection-2026-05-16.md)** Backlog status-drift auditor — detect `status: open` rows whose primary artifact has already shipped
- [ ] **[B-0557](backlog/P3/B-0557-audit-backlog-status-drift-quality-improvements-2026-05-16.md)** Audit-backlog-status-drift — quality improvements per PR #3758 reviewer findings

<!-- END AUTO-GENERATED -->
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
id: B-0557
priority: P3
status: open
title: "Audit-backlog-status-drift — quality improvements per PR #3758 reviewer findings"
tier: factory-hygiene
effort: S
created: 2026-05-16
last_updated: 2026-05-16
depends_on: [B-0553]
composes_with: [B-0553]
tags: [audit, hygiene, follow-up, reviewer-findings, mechanization]
type: feature
---

# Audit-backlog-status-drift — quality improvements

## Origin

Follow-up to [B-0553](B-0553-audit-backlog-status-drift-detection-2026-05-16.md). PR #3758 (the audit-tool implementation) attracted 4 additional reviewer findings on the e5e7143 commit (after the regex-order + SKIP_SECTIONS + nested-headings fixes). Each finding is valid; this row captures them as a follow-up slice rather than continuing the iteration treadmill on the original PR.

## The four findings

### Finding 1 — Mixed-bullet path extraction (Codex P1, PR #3758 line 172)

> Do not skip the entire line when an inline cross-reference token appears.

Current behaviour: `INLINE_CROSSREF_PATTERNS.some((re) => re.test(line))` skips the WHOLE line. This is correct for pure cross-reference bullets like `- Composes with \`tools/x.ts\`` but is overly aggressive for mixed bullets like `- Add \`tools/new.ts\` per [B-0123] convention` — the path `tools/new.ts` is a deliverable, the `per [B-0123]` is just a citation.

**Proposed fix**: only skip the line if the cross-ref keyword appears at the START of the bullet (after `^[\s*-]+\s+`). Mid-line cross-refs allow extraction.

### Finding 2 — cwd-relative path resolution (Copilot P1, PR #3758 line 212)

> Tool assumes cwd = repo root. `enumerateOpenRows()` reads `docs/backlog/...` and `findDriftCandidates()` calls `existsSync(p)` on relative paths. If invoked from another cwd, the paths fail to resolve.

**Proposed fix**: detect repo root via `git rev-parse --show-toplevel` (or accept it as a CLI flag with default), then resolve all paths absolute from that root. Use `path.resolve(repoRoot, p)` in `existsSync` calls.

### Finding 3 — Error handling on read failures (Copilot P1, PR #3758 line 202)

> `enumerateOpenRows()` can throw and abort the whole run on a single unreadable/edge-case backlog file (e.g., permission denied, transient FS error).

**Proposed fix**: wrap `readFileSync` in `try/catch`; on failure, emit a stderr warning naming the failed file and skip that row (continue with the next). The full audit completes; the operator sees the partial-result warning.

### Finding 4 — `--check` mode for CI integration (Copilot P1, PR #3758 line 277)

> B-0553 spec calls for a CI-friendly "check/enforce" mode (non-zero exit when drift candidates are found). `main()` currently always exits 0.

**Proposed fix**: add `--check` flag. When set, `main()` returns 0 if no candidates, non-zero (e.g., 65) if any candidates. Default behaviour (detect-only) unchanged.

## Acceptance

- Mixed-bullet path extraction: extracts deliverables from bullets that contain cross-ref tokens mid-line; skips only bullets that START with cross-ref keywords. Regression tests cover both cases.
- cwd-independent path resolution: tool runs correctly from any directory; verified via `cd /tmp && bun /path/to/tools/hygiene/audit-backlog-status-drift.ts`.
- Error handling: tool emits stderr warning + continues on unreadable files; verified via mock fixture with a permission-denied file.
- `--check` mode: returns non-zero when candidates present; default mode unchanged. CI wire-up can later add this flag to a GitHub Actions cron.
- 16-test baseline preserved; ≥3 new regression tests for the new behaviour.

## Composes with

- [B-0553](B-0553-audit-backlog-status-drift-detection-2026-05-16.md) — parent row (audit-tool spec + first-slice impl)
- PR #3758 reviewer findings (verbatim references above)
- [`.claude/rules/backlog-item-start-gate.md`](../../../.claude/rules/backlog-item-start-gate.md) step 0 — the discipline the audit tool mechanizes
- [`memory/feedback_substrate_drift_catch_pattern_*.md`](../../../memory/) — the discipline substrate

## Why P3

Same as B-0553 — friction-reducer. Each improvement is small and additive; together they take the tool from "first slice" to "operationally robust." Not P2 because the first slice already works for the common case (30+ live candidates detected correctly). Not P4 because the findings are concrete + actionable + recent.

## Non-goals

- `--prune-claims` flag (still deferred per B-0553)
- `--open-close-pr` flag (still deferred per B-0553)
- Cross-row drift detection (per B-0532 — different audit)
- Auto-merge integration (separate concern; the tool is a detector, not an actor)

## Origin tick

Tick 15 of the 2026-05-16 session. PR #3758 review-cycle 2 produced these 4 findings on commit `e5e7143`; this row captures them as follow-up substrate so PR #3758 can ship cleanly.
68 changes: 68 additions & 0 deletions docs/hygiene-history/ticks/2026/05/16/0554Z.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Tick 2026-05-16T05:54Z — Otto-CLI

Sixteenth tick of the resume-session series. Brief-ack #3 of new
counter cycle. Rate limit at 0/5000; **reset in 1 min** — sweep
deferred one more tick to land cleanly inside the reset window.

## Refresh result

| Surface | State |
|---|---|
| Cron sentinel | Alive (`bd1c7739`) |
| Rate limit (GraphQL) | **0/5000 remaining**; **reset in ~1 min** (~05:55Z UTC) |
| `origin/main` | Unchanged from last 4 ticks (`268f351`) |
Comment thread
AceHack marked this conversation as resolved.
| Branches awaiting post-reset PR creation | 8 (see prior shard) |

## Why no PR sweep this tick

Reset is 1 min away. Firing 8 `gh pr create` + 8 `gh pr merge --auto` + 1 `gh pr close` (~80-100 GraphQL calls) RIGHT BEFORE the reset would either:

- Fail with `rate limit already exceeded` on the first call (and waste the rest)
- Partially succeed if the reset fires mid-sweep (and leave the queue in a mixed state)

Cleaner: wait one tick for the reset to be solid, then execute the
sweep at the start of the next tick when the full 5000 budget is
available.

This is the right substrate-cost tradeoff: one extra brief-ack now
prevents a half-executed sweep that would need recovery work.

## Sentinel + close

`CronList`: `bd1c7739` alive.

## Next tick plan (pinned, ready to execute)

```bash
# 1. Verify rate limit is healthy
gh api rate_limit --jq '.resources.graphql.remaining'

# 2. Open ~8 deferred PRs (in order of substrate priority).
# Exact count depends on peer-Otto convergence (peer's PR #3759
# bundled my 0545z shard during the rate-limit-zero window, so
# that branch's PR was no longer needed):
# a. chore/refresh-pr-gate-rate-limit-tiers (rule extension; most substantive)
# b. chore/b0532-status-section-partial-completion (audit)
# c. chore/b0533-status-section-partial-completion (audit)
# d-h. shard PRs from the rate-limit-zero ticks
# (0528z + 0535z + 0540z + 0548z + 0554z = 5 shards if peer
# bundled 0545z; 6 if not — verify which branches are still
# pending via `gh pr list --head shard/tick-...` before
# firing each create)

# 3. Arm auto-merge on each via explicit PR number

# 4. Close PR #3746 as superseded by PR #3757 with brief explanatory comment

# 5. Write a normal tick shard documenting the sweep
```

Budget needed: ~80-100 GraphQL calls; fits comfortably in 5000.

## Visibility signal

- Brief-ack #3 of new counter cycle (within 1-2 acceptable tier)
- Rate limit at 0/5000; reset in 1 min
- 9 branches pushed and awaiting PR creation (8 from prior ticks + this shard)
- Sentinel `bd1c7739` alive
- Post-reset PR sweep plan pinned and ready
Loading