Skip to content

feat(cc030): validate.sh closure-date + CHANGELOG drift (Index↔Section bidirectional)#65

Merged
screenleon merged 6 commits into
mainfrom
feat/cc030-validator-bidir-changelog-drift
May 16, 2026
Merged

feat(cc030): validate.sh closure-date + CHANGELOG drift (Index↔Section bidirectional)#65
screenleon merged 6 commits into
mainfrom
feat/cc030-validator-bidir-changelog-drift

Conversation

@screenleon
Copy link
Copy Markdown
Owner

Summary

Implements CC-030pm/scripts/validate.sh Index ↔ Section bidirectional consistency + CHANGELOG drift detection.

New error tokens

  • E-CLOSURE-DATE-MISMATCH — index closure date (✅ closed YYYY-MM-DD / 🚫 dropped YYYY-MM-DD) must match either a heading marker (## CC-X — title ✅ YYYY-MM-DD, exact terminator) OR a **Outcome**: YYYY-MM-DD ... line where the date is immediately after the literal prefix. Mid-sentence dates ignored.
  • E-CHANGELOG-DRIFT — every pr:#NN (or pr:owner/repo#NN) inside CHANGELOG.md's ## [Unreleased] section must correspond to a backlog row whose status is closed. Active / deferred / dropped / missing all drift.

CLI surface

validate.sh <BACKLOG.md> [DECISIONS.md] [CHANGELOG.md] — third arg optional; sibling CHANGELOG.md auto-detected when omitted, regardless of whether DECISIONS.md is supplied. Backwards compatible with all existing call sites.

Why

Existing validator only enforced Index-row format. CC-030 strengthens it to catch:

  1. Closed entries with missing or mismatched closure dates (exposed real drift in CC-027/028/034 + CC-029 that PRs chore(backlog): close CC-029 + backfill See stubs for CC-027/028/034 #60/chore(backlog): add YYYY-MM-DD to CC-027/028/034 Outcome lines #62/chore(backlog): normalize CC-029 Outcome line to date-prefix shape #63 patched)
  2. CHANGELOG [Unreleased] entries that reference PRs not actually shipped
  3. Trailing junk after heading marker dates (✅ 2026-05-15 garbage no longer silently truncated)

Schema audit table delivered in commit fix(cc030): cross-repo PR refs covers all Refs prefixes, status enum, and closure markers from pm/schema.md — no further divergence detected.

Gate history

6 PR-gate rounds (r1 NO-GO → r6 GO). Sequence:

Round Verdict Issues found
r1 NO-GO CHANGELOG drift didn't check closed-status; closure-date regex too permissive
r2 NO-GO Legacy 2-arg validate.sh BACKLOG DECISIONS skipped sibling CHANGELOG auto-detect
r3 NO-GO Heading marker accepted trailing junk; Outcome fallback regex too loose
r4 NO-GO Schema permits pr:owner/repo#NN but regex only matched pr:#NN; needed dropped-row fixture
r5 NO-GO Missing positive fixture for Outcome-date fallback (only had negative)
r6 GO All blocks resolved; critic + arch advise only (helper/awk-pass dedup → CC-046)

Data prep PRs landed first to keep main's validator output stable: #60 (CC-029 close + See stubs), #62 (CC-027/028/034 Outcome dates), #63 (CC-029 Outcome reorder).

Follow-up

Test plan

  • bash pm/scripts/test/run-tests.sh → 23 passed / 0 failed
  • bash pm/scripts/validate.sh BACKLOG.md → only pre-existing 3-token baseline (CC-035 E-INDEX-MISMATCH, CC-038 E-AREA-ENUM + E-REFS-PREFIX; tracked separately)
  • PR-gate r6 GO — critic advise, qa-tester approve, architecture-reviewer advise

🤖 Generated with Claude Code

screenleon and others added 6 commits May 16, 2026 20:41
Extend pm/scripts/validate.sh with two new structural checks:

- E-CLOSURE-DATE-MISMATCH: when index row is `✅ closed YYYY-MM-DD`
  (or `🚫 ...` dropped), require the body section to carry the same
  date — either as a heading marker (`## CC-XXX — title ✅ YYYY-MM-DD`)
  or as any YYYY-MM-DD token within the section when **Outcome**: is
  present. Catches the drift class where Index says closed but body
  has no closure metadata (or disagrees on date).

- E-CHANGELOG-DRIFT: optional second pass that runs when CHANGELOG.md
  is given as third positional arg or auto-detected as sibling of
  BACKLOG.md. For each pr:#NN token under `## [Unreleased]`, require
  some backlog Index row to reference that PR; symmetric direction
  (closed but unmentioned) intentionally out of scope.

CLI surface extended additively:
  validate.sh <BACKLOG.md> [DECISIONS.md] [CHANGELOG.md]
2-arg form unchanged; missing sibling CHANGELOG silently skips
drift pass; explicit nonexistent path returns exit 2 (matches
DECISIONS file behavior).

Fixtures added: bad-closure-date-mismatch/ and bad-changelog-drift/
with explicit want_token entries in run-tests.sh. Existing fixtures
unchanged; 13 passed / 0 failed. Repo BACKLOG.md continues to emit
only the pre-existing 3 token set (E-INDEX-MISMATCH, E-AREA-ENUM,
E-REFS-PREFIX) tracked separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR-gate gate-20260516-195358 found two convergent issues:

- CHANGELOG drift only checked PR token presence in Refs, not status:
  `[Unreleased]` could reference an `🔵 active` row and silently pass.
  Strengthen rule so `pr:#NN` MUST map to at least one backlog row
  with status `closed`. Missing / active / deferred / dropped / done
  all emit E-CHANGELOG-DRIFT (Branches A–E from proactive sweep).
- Closure body date fallback scanned any YYYY-MM-DD in the section,
  including Source / Why / Note lines. Tighten to heading marker
  (`✅ YYYY-MM-DD` / `🚫 YYYY-MM-DD`) OR a date on a `**Outcome**:`
  line only.

New fixture bad-changelog-drift-active-ref/ exercises Branch C
(Unreleased ref → active row). Existing bad-changelog-drift
remains the canonical Branch A (missing ref) case. Existing
bad-closure-date-mismatch confirmed still emits exactly
E-CLOSURE-DATE-MISMATCH under the tightened extraction scope.

run-tests.sh: 14 passed / 0 failed.

The strengthened closure-date rule exposes pre-existing data drift
on the live BACKLOG (CC-027 / CC-028 / CC-034 Outcome lines lack
YYYY-MM-DD; previously masked by Source-line dates). That data
drift is intentionally left for a separate data-prep PR before
merging this branch, per the established split between data and
validator-code changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…gate r2)

PR-gate r2 (gate-20260516-201444) found a real regression: callers
using the legacy `validate.sh BACKLOG.md DECISIONS.md` form silently
lost CHANGELOG drift detection. Root cause was line 41's overly
strict guard `[ -z decisions ] && [ -z changelog ]` — sibling
CHANGELOG auto-detect only ran when BOTH optional args were empty.

Fix: condition relaxed to `[ -z changelog ]` only. DECISIONS arg
presence no longer suppresses CHANGELOG auto-detection.

Branch sweep across all CLI invocation forms (CLI-1..CLI-6):
- 1 arg, auto-detect: unchanged (works)
- 2 args (DECISIONS), auto-detect sibling: NOW works
- 3 args explicit DECISIONS+CHANGELOG: unchanged (works)
- 3 args empty DECISIONS + explicit CHANGELOG: unchanged (works)
- 3 args explicit but nonexistent CHANGELOG: unchanged (exit 2)
- 2 args, sibling CHANGELOG missing: unchanged (silent skip)

Test coverage:
- T1: explicit 3-arg drift via run_validate_case_multi helper
- T2: legacy 2-arg + sibling CHANGELOG drift (regression-fix case)
- T3: explicit nonexistent CHANGELOG -> exit 2 + E-SCHEMA-HEADER

run-tests.sh: 17 passed / 0 failed. New minimal DECISIONS.md
fixture under bad-changelog-drift/ enables T2 without disturbing
the existing single-arg case.

Architect-medium (status normalize dedup) and architect-low
(flag-based CLI) deferred to follow-up backlog entries.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e (gate r3)

PR-gate r3 (gate-20260516-202620) found two regression boundaries:

- Body-marker date capture in note_body_closure_date silently
  truncated trailing junk: `## CC-X ✅ 2026-05-15 garbage` was
  accepted as exact date. Restore strict shape: heading marker
  must terminate after the 10-char date (only trailing whitespace
  permitted). Trailing non-whitespace content emits E-DATE-FORMAT.
- Outcome-line fallback scanned for any YYYY-MM-DD anywhere in
  the line; mid-sentence dates accidentally satisfied closure
  evidence. Tighten regex to require the date IMMEDIATELY after
  the literal `**Outcome**: ` prefix. Mid-line dates no longer
  count as closure evidence — they must be in the canonical
  position or in the heading marker.

Coverage gaps closed:
- good-changelog-closed-ref/: [Unreleased] referencing a closed
  backlog PR exits 0 cleanly (CL-B happy path).
- bad-closure-date-trailing-junk/: BD-3 boundary -> E-DATE-FORMAT.
- bad-outcome-date-misplaced/: BD-7 boundary
  -> E-CLOSURE-DATE-MISMATCH.

run-tests.sh: 20 passed / 0 failed.

The tightened Outcome regex newly flags CC-029's Outcome line
("**Outcome**: PR #57 合併 2026-05-15;" — date is not at the
prefix position). That data drift is intentionally left for a
separate data-prep PR before merging this branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e (gate r4)

PR-gate r4 (gate-20260516-204121) flagged two convergent gaps and
one cleanup:

- F1 (critic+qa high): schema permits pr:owner/repo#NNN (schema.md:96)
  but validator regex only matched pr:#NNN. Cross-repo refs in
  [Unreleased] silently passed when active row referenced them.
  Fix: unify PR-token regex
  `pr:([A-Za-z0-9._-]+/[A-Za-z0-9._-]+)?#[0-9]+` across both passes
  (backlog refs collection + [Unreleased] scan). Cross-repo and
  simple tokens kept as DISTINCT keys (pr:#100 != pr:owner/repo#100).
- F2 (qa medium): bad-closure-date-dropped-mismatch/ fixture covers
  the 🚫 dropped branch of the closure-date check.
- F3 (critic low): removed unused date_token() helper added in
  fix-r3.

Schema audit table (delivered in dispatch final message) confirms
no further divergence between schema.md and validator behavior:
Refs prefixes, status enum, closure markers all aligned. Audit
recorded for follow-up reviewers.

Branch sweep XR-1..XR-6 (cross-repo refs) verified:
- XR-2 (cross-repo closed-row match) PASS
- XR-3 (no matching row) drift
- XR-4 (cross-repo active row) drift
- XR-5 (mixed simple+cross) drift per token
- XR-6 (malformed pr:#owner/repo) ignored gracefully

run-tests.sh: 22 passed / 0 failed (20 prior + 2 new).
Repo BACKLOG.md validation token set unchanged (3 pre-existing).

Deferred to follow-up backlog (architect-medium status-normalize
dedup; qa-low docstring style) will be recorded by main-thread.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR-gate r5 (gate-20260516-205441) — only qa-tester blocked: the
Outcome-date fallback branch had negative coverage
(bad-outcome-date-misplaced rejects mid-sentence dates) but no
positive fixture proving the canonical accepted shape.

Add good-closure-outcome-date/ — closed row with no heading
marker, Outcome line "**Outcome**: 2026-05-01 — ..." date right
after the prefix → exit 0, empty stderr.

Critic + qa low (docstring style) and critic + arch low
(run_validate_case_multi duplicates run_validate_case) deferred
to follow-up backlog; will be recorded by main-thread.

run-tests.sh: 23 passed / 0 failed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@screenleon screenleon merged commit 11dfb17 into main May 16, 2026
9 checks passed
@screenleon screenleon deleted the feat/cc030-validator-bidir-changelog-drift branch May 18, 2026 00:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant