Skip to content

refactor(doc-sweep): unify doc classification behind one shared module + exempt categories - #9

Merged
nthansen merged 17 commits into
mainfrom
unify-doc-classification
Jul 4, 2026
Merged

refactor(doc-sweep): unify doc classification behind one shared module + exempt categories#9
nthansen merged 17 commits into
mainfrom
unify-doc-classification

Conversation

@nthansen

@nthansen nthansen commented Jul 4, 2026

Copy link
Copy Markdown
Owner

What

Collapses doc-sweep's three disagreeing "what is a doc?" definitions into one shared, dependency-free classifier, and adds an exempt-categories concept so the staleness guards stop nagging on changes that provably don't need docs.

The bug this fixes

The two guards (docs-ci-check.sh, revise-push-guard.sh) each carried a copy-pasted is_doc, and both diverged from the audience-rules: .claude/**/*.md were docs per the rules but non-docs per the scripts — so a PR editing only .claude/context/audience-rules.md failed the docs-staleness check. Fixed and regression-locked in all three test suites.

Changes

  • New plugins/doc-sweep/hooks/doc-classify.mjs — one node classifier (in-house glob matcher, no deps, node --test). Both guards delegate to it; neither carries inline patterns.
  • Three-way classificationdoc / exempt / doc-requiring. New exemptPatterns (default: common test globs) means a test-only change passes without a [skip docs] ack, while tests + real source still enforce. Precedence: excludeDirs → exemptPatterns → docPatterns.
  • docMode retired for a docPatterns glob list (BREAKING for stale configs — re-run the installer or rename the field). Default doc-set now includes .claude/**/*.md.
  • Installers vendor doc-classify.mjs alongside the script, record docPatterns, and re-copy on reconfigure / remove on uninstall.

How it was built & verified

Planned and implemented via the superpowers-bridge OpenSpec schema (brainstorm → proposal → design → specs → tasks → plan → subagent-driven TDD → verify → retrospective → archive). Every task got an adversarial per-task review plus a final whole-branch adversarial review — which caught three real defects green tests missed, all fixed with new regression tests:

  • a merge-commit [skip docs] bypass in the push guard,
  • an unguarded JSON parse that silently masked bad classifier output,
  • an install-reconfigure path that left the classifier missing → permanent silent fail-open.

Local gate green: doc-classify unit (13/13), both shell suites, marketplace policy, skill-gate (7/7, both install benchmarks 1.0), openspec validate --strict --all (5/5), openspec hygiene. ShellCheck runs in CI.

🤖 Generated with Claude Code

nthansen and others added 17 commits July 3, 2026 18:44
Propose artifacts for collapsing the three disagreeing doc-classification
definitions into one shared doc-classify.mjs module + a declarative docPatterns
source, fixing the .claude/** divergence bug and retiring docMode.

brainstorm/proposal/design/specs/tasks/plan generated via the superpowers-bridge
schema. Apply phase (worktree + subagent-driven TDD) to follow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fy-doc-classification

Adds an exemptPatterns concept to the doc-classification design: a three-way
doc / exempt / doc-requiring split. Default exempts common test globs, so a
test-only PR passes without a [skip docs] ack while tests + real code still
enforce. Distinct from excludeDirs by intent; configurable per project.

Threads through brainstorm (Q6), proposal, design (D7), the doc-classification
spec (+ docs-staleness-ci test-only scenario), tasks, and plan (classifier code,
tests, audience-rules block).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Malformed classifier JSON previously fell through to two ungarded
node -e parses and silently exited 0 with no diagnostic; now a single
guarded parse fails closed to a warn+fail-open path. Config paths with
spaces previously word-split via the unquoted $cfg_arg (SC2086
disabled) and silently fell back to default patterns; now cfg_arg is
a bash array so the path stays one argument.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-safe array expansion

The per-commit [skip docs] loop in revise-push-guard.sh used `git diff-tree -r`
for every commit, which prints nothing for merge commits, so a non-doc file
introduced only by a merge (e.g. a conflict resolution) fell through to allow.
Detect merge commits (2+ parents) and use the combined-diff form (-c) instead,
which surfaces only merge-unique content, leaving non-merge commit behavior
unchanged and avoiding over-flagging a clean auto-merge with no merge-unique
change. Also replace "${cfg_arg[@]}" with the bash-3.2-safe
${cfg_arg[@]+"${cfg_arg[@]}"} idiom at all three call-sites (revise-push-guard.sh
x2, docs-ci-check.sh x1) so an empty array doesn't abort under `set -u` on
macOS's stock bash.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…classify.mjs (add go/py test globs)

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

Both install skills (install-docs-ci, install-revise-hook) now copy the
shared doc-classify.mjs classifier alongside the script/hook they scaffold,
since the guard scripts resolve it via $here/doc-classify.mjs and would
otherwise fail open at runtime. The generated config JSON now writes a
docPatterns glob list instead of the retired docMode string, matching the
default documented in context/audience-rules.md. Uninstall now also removes
the vendored doc-classify.mjs. Evals updated to match.
… custom docPatterns to audience-rules

Reconfigure in both install-docs-ci and install-revise-hook now unconditionally
re-copies doc-classify.mjs alongside the script/hook, so a missing or stale
classifier self-heals even when nothing else about the install changed
(previously it silently stayed absent, and docs-ci-check.sh/revise-push-guard.sh
fail open on every guarded action without it). Existence-detection steps now
call out the classifier as part of what constitutes an install.

Also aligns audience-rules.md's docPatterns-persistence claim with actual
installer behavior: docPatterns is now persisted to
.claude/context/audience-rules.md (and mirrored into the per-install config
JSON) only when the user picks a custom doc-file-set; the default choice omits
the key entirely so doc-classify.mjs's built-in default applies, instead of
transcribing the step-2 parenthetical (which was missing the `**/` prefix the
real defaults use). Reconfigure evals for both skills now assert the
classifier re-copy so a regression here is caught by the skill gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…classifier changes

Both installers' evals now assert classifier-copy + docPatterns behavior; benchmarks
regenerated via real eval runs (install-docs-ci 18/18, install-revise-hook 19/19).
Corrected one install-docs-ci assertion that wrongly required docPatterns for the
default choice (the skill correctly omits it so the built-in default applies).

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

Wires doc-classify.test.mjs into validate.yml next to the other node --test
steps, and updates doc-sweep's README/CHANGELOG for the classifier unification:
docMode retirement in favor of docPatterns (BREAKING for stale configs), the
shared doc-classify.mjs backing both guards, the .claude/** default fix, and
the new exemptPatterns behavior (test-only changes pass without an ack).
…correct exemptPatterns config docs

M1: README/CHANGELOG claimed exemptPatterns was an installer prompt like docPatterns;
only docPatterns is prompted. exemptPatterns is config-JSON-only (hand-edit, read via
--config). M2: an excludeDirs entry with a trailing slash (e.g. "vendor/") silently
failed to exclude paths under it; doc-classify.mjs now strips trailing slashes before
comparing. M3: the spec said an empty configured docPatterns/exemptPatterns list
"replaces the default" without qualification; code (correctly, for safety) treats
empty/omitted as "use built-in default" so an empty docPatterns can't fail every PR.
Spec now says so explicitly, with scenarios. Added node --test coverage for all three.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Creates the doc-classification living spec, folds the docs-staleness-ci and
revise-docs-push-guard deltas into their living specs, and moves the change to
archive/2026-07-04-unify-doc-classification.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@nthansen
nthansen merged commit 933dff1 into main Jul 4, 2026
2 checks passed
@nthansen
nthansen deleted the unify-doc-classification branch July 4, 2026 01:25
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