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
115 changes: 0 additions & 115 deletions .claude/hooks/doc-sweep-revise-push.sh

This file was deleted.

1 change: 0 additions & 1 deletion .claude/hooks/doc-sweep-revise.json

This file was deleted.

16 changes: 1 addition & 15 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -1,15 +1 @@
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "c:/git/funbox-plugins/.claude/hooks/doc-sweep-revise-push.sh c:/git/funbox-plugins/.claude/hooks/doc-sweep-revise.json"
}
]
}
]
}
}
{}
29 changes: 29 additions & 0 deletions .github/workflows/docs-staleness.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: docs-staleness

# Dogfoods doc-sweep's PR-time docs-staleness check on funbox itself: fails a PR when a non-doc
# file changed but no documentation did, unless the author adds a `[skip docs]` token to a commit
# message or the PR body. Deterministic — no secrets, no Anthropic auth (same stance as validate.yml).
# Runs the canonical in-repo script directly (funbox is doc-sweep's source, so no vendored copy).
#
# NOT a required status check for now — advisory (a red X does not block merge) while we live with
# it before deciding whether to retire the local push-guard hook. See CLAUDE.md.
on:
pull_request:

permissions:
contents: read

jobs:
docs-staleness:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0 # full history so the merge base and commit messages resolve

- uses: actions/setup-node@v6
with:
node-version: '20'

- name: Docs staleness check
run: bash plugins/doc-sweep/hooks/docs-ci-check.sh
23 changes: 16 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,22 @@ self-contained under `plugins/`.
- `doc-sweep`'s `init-audience-rules` skill is **`disable-model-invocation: true` on purpose**
(manual-only `/`-command): auto-invocation over-triggers on ordinary CLAUDE.md-vs-README doc
talk. Don't remove it — `revise-docs`/`audit-docs` stay model-invocable.
- This repo also dogfoods doc-sweep's **revise-docs push guard** (project scope): a `PreToolUse`
Bash matcher in `.claude/settings.json` runs `.claude/hooks/doc-sweep-revise-push.sh` (config
`doc-sweep-revise.json`: `trigger=push`, `repoScope=doc-sweep-only`, `docMode=default`) and
**blocks a Claude-driven `git push`** when a non-doc file changed since docs were last reviewed.
Clear it by running `/doc-sweep:revise-docs-and-mark` (advances the per-clone marker at
`$(git rev-parse --git-common-dir)/doc-sweep-revise-marker`, not committed). One-shot bypass:
`DOC_SWEEP_REVISE_SKIP=1` or `--no-verify`. Reconfigure/uninstall via `/doc-sweep:install-revise-hook`.
- This repo dogfoods doc-sweep's **docs-staleness CI check** (the primary docs guard):
`.github/workflows/docs-staleness.yml` runs `plugins/doc-sweep/hooks/docs-ci-check.sh` on every
PR and **fails** when a non-doc file changed but no docs did — unless a **`[skip docs]`** token
(mirrors `[skip ci]`) appears in a commit message or the PR body. Deterministic (no secrets, no
Anthropic auth — same stance as `validate.yml`). It is currently **advisory** (not a required
status check) while we live with it. Baseline is the PR merge base (no marker). Ship/scaffold it
into another repo via `/doc-sweep:install-docs-ci` (vendors the script under `.github/doc-sweep/`).
It catches what the local hook can't: human commits, contributors without doc-sweep, and fork PRs.
- The local **revise-docs push guard** (`revise-push-guard.sh`, installed via
`/doc-sweep:install-revise-hook`) remains a shipped doc-sweep capability — an optional *pre-push*
fast-feedback loop that gates only Claude-driven pushes in one clone, and honors the same
`[skip docs]` token (every non-doc commit in the marker range must carry it). **funbox no longer
installs it** — CI is the primary guard here. Whether to **retire the hook entirely is a deferred
decision**: revisit once the CI check has proven itself; don't delete the hook without that call
(re-deriving it + its tests is real work). One-shot local bypass stays `DOC_SWEEP_REVISE_SKIP=1`
or `--no-verify`.

## Versioning — read before touching a version

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-07-03
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
## Context

doc-sweep's current drift guard is a local `PreToolUse` push hook (`revise-docs-push-guard`).
By construction it only gates **Claude-driven** pushes, **in the one clone** where it was
installed, and relies on a **per-clone, uncommitted marker** (`$(git rev-parse
--git-common-dir)/doc-sweep-revise-marker`). It therefore cannot enforce docs review on human
commits, on contributors who never installed doc-sweep, or on **fork PRs** — the cases where
drift most often slips in. The repo already runs a CI gate (`.github/workflows/validate.yml`)
that deliberately uses **no Anthropic auth** and stays deterministic; a docs check should fit
that same mold. doc-sweep is a published marketplace plugin whose install skills
(`install-revise-hook`) are manual-only (`disable-model-invocation: true`) because
auto-invocation over-triggers on ordinary doc talk.

## Goals / Non-Goals

**Goals:**
- A universal, PR-time enforcement layer that catches everyone the hook cannot (humans, forks,
non-doc-sweep contributors).
- Zero false-positive friction for legitimate code-only PRs (bug fixes) via a cheap, in-browser
ack that needs no history rewrite.
- Deterministic and secret-free, matching validate.yml's stance.
- One shared acknowledgment vocabulary across the hook and the CI check.
- Package it the doc-sweep way: a manual installer skill + a shipped script.

**Non-Goals:**
- Removing/deleting the push-guard hook (deferred, reversible follow-up — recorded as a note).
- Any LLM-in-CI judgement of doc necessity (needs a secret + tokens; rejected).
- `vscode-thinking-display`; release/changelog automation (anti-goals for a rolling-`main` repo).

## Decisions

**1. PR-time check keyed on the merge base — no committed marker.**
The hook needs a marker because it watches an ongoing working tree across many commits. A PR has
a natural baseline: its merge base. So the check is simply "in the merge-base..head diff, did
non-doc change without docs?" — no marker file, no advance ceremony, no merge conflicts.
*Alternative considered:* a committed "docs reviewed up to <sha>" marker mirroring the hook.
Rejected as needless churn and conflict surface once the merge base gives a free baseline.

**2. Block, but clear via a `[skip docs]` ack (block + escape hatch).**
The check asserts "docs staleness was *considered*," not "docs ARE stale" (unknowable without an
LLM, and wrong for bug fixes). A code-only PR fails until the author signals consideration.
*Alternatives:* advisory-only (too weak — ignored); path-scoped trigger (crude proxy); LLM-judged
(needs secret/tokens). Rejected in favor of the deterministic ack.

**3. Ack is a single `[skip docs]` token in a commit message OR the PR body.**
One token, placed in any commit message in the PR range (git-native) or in the PR body (the
forgot-it fallback — editable in-browser with **no history rewrite**). Chosen to mirror the
familiar `[skip ci]` convention, so there's nothing new to learn; a plain bracket token works
uniformly in both a commit message and a PR body, avoiding the trailer-vs-body split an earlier
`Docs-N/A:` trailer design would have forced. This is the direct answer to "what if someone does
a bunch of work and forgets the token." Docs actually changing is always an implicit pass.

**4. Shared token with the hook.**
The same `[skip docs]` token (in a commit message) also clears the local hook (added to its bypass
set). Two guards, one token — nothing new to learn, no divergence to document.

**5. Package as a manual install skill + shipped script (mirror `install-revise-hook`).**
New skill `install-docs-ci` (`disable-model-invocation: true`, scoped `allowed-tools`) scaffolds
`.github/workflows/*.yml` into the user's repo; the workflow calls
`plugins/doc-sweep/hooks/docs-ci-check.sh`. Parsing uses `node` (runners have it; consistent with
the hook's no-`jq` rule). *Alternatives:* a reusable/composite `uses:` action (couples users to
funbox's ref + pins — supply-chain surface the repo avoids); doc-only recipe (every user
reimplements the diff/ack parsing). Rejected.

**6. Reposition the hook as optional secondary; funbox stops requiring it.**
CI becomes the primary guard. The hook stays shipped for users who want fast pre-push feedback,
but funbox's own `.claude/settings.json` drops the required local hook so the sole maintainer
isn't carrying the marker ceremony day-to-day. Whether to retire the hook entirely is deferred
until CI has been lived with — recorded as a `CLAUDE.md` / "considered & revisit" note, not
decided here (deletion is the one hard-to-reverse move under current uncertainty).

## Risks / Trade-offs

- **Feedback moves from pre-push to PR time** (a few CI minutes later) → the hook remains
available for anyone who wants the earlier local loop.
- **Ack could be used to rubber-stamp every PR** → a `[skip docs]` token is visible and auditable
in the commit/PR, so waving it through shows up in review; acceptable for a solo/low-traffic
repo, revisit if abused.
- **Two guards until the hook's fate is decided** → mitigated by the shared vocabulary and by
funbox no longer requiring the hook, so there's no day-to-day double friction.
- **Doc-set drift between hook config and CI config** → both read the same doc-file-set default
(`CLAUDE*.md`, `README*.md`, `CHANGELOG.md`, `docs/**`); keep them documented in one place.

## Migration Plan

1. Land the shipped script + `install-docs-ci` skill + push-guard shared-ack update (with tests).
2. Dogfood: scaffold the workflow into funbox's `.github/workflows/`; drop the required local hook
from funbox `.claude/settings.json`; update funbox `CLAUDE.md` with the CI guard + deferred
retirement note.
3. Update doc-sweep `README.md`/`CHANGELOG.md`; regenerate the new skill's `/skill-gate` benchmark.
4. Rollback: the change is additive and opt-in — remove the scaffolded workflow and revert the
push-guard bypass addition; nothing else depends on it.

## Open Questions

- None blocking. The retire-vs-keep-the-hook decision is intentionally deferred (see Decision 6).
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
## Why

doc-sweep's only drift guard today is a local `PreToolUse` push hook: it catches **only
Claude-driven pushes, only in the clone where it's installed**. It structurally cannot enforce
docs review on human commits, on contributors who don't run doc-sweep, or on **any pull request
from a fork**. A PR-time CI check is the one layer that catches everyone — and doc-sweep, being
the documentation-drift plugin, is the natural home for it.

## What Changes

- **New CI docs-staleness check.** A deterministic (no-LLM, no API secret) script that, on a
pull request, fails when non-doc files changed but no doc file changed — *unless* the author
acknowledges "docs considered, none needed." Baseline is the PR's **merge base**, so it needs
no committed marker or state file.
- **Explicit ack token** so a legitimate bug fix is never a hard blocker: a single
`[skip docs]` token (mirroring the familiar `[skip ci]` convention) placed in **any commit
message in the PR range** *or* in the **PR body** (the forgot-it fallback, editable in-browser
with no history rewrite) — or an actual doc change (implicit pass).
- **New manual install skill** `install-docs-ci` (`disable-model-invocation: true`, mirroring
`install-revise-hook`) that scaffolds a `.github/workflows/*.yml` into a user's repo calling a
doc-sweep-shipped script; supports reconfigure/uninstall.
- **Unify the existing push-guard's ack vocabulary** with the CI check: the same `[skip docs]`
token that clears CI also clears the local hook, so the two guards speak one language.
- **Reposition the push-guard as optional/secondary** (CI becomes the primary guard). The hook
stays shipped; **funbox itself stops requiring it** locally. Retiring the hook entirely is a
deliberately deferred follow-up, recorded as a note — not done in this change.
- funbox **dogfoods** the new check (adds the workflow to this repo).

Non-goals (out of scope): removing/deleting the push-guard hook; any LLM-in-CI judgement;
`vscode-thinking-display`; release/changelog automation.

## Capabilities

### New Capabilities
- `docs-staleness-ci`: a PR-time, deterministic docs-vs-code drift check plus its manual
installer skill and shipped script; defines the pass/fail logic, the ack vocabulary, and the
install/reconfigure/uninstall contract.

### Modified Capabilities
- `revise-docs-push-guard`: the local hook SHALL recognize the shared `[skip docs]` ack
token defined by `docs-staleness-ci`, and is repositioned as an optional secondary guard
(funbox no longer requires it). No change to its marker/snapshot mechanism.

## Impact

- **New files**: `plugins/doc-sweep/hooks/docs-ci-check.sh` (+ a test harness alongside the
existing `test-revise-push-guard.sh`), `plugins/doc-sweep/skills/install-docs-ci/SKILL.md`,
a scaffolded workflow under funbox's own `.github/workflows/`.
- **Modified**: doc-sweep `README.md`/`CHANGELOG.md`; the push-guard hook script + its spec
(shared ack); funbox `.claude/settings.json` (drop the required local hook); funbox `CLAUDE.md`
(document CI guard + deferred-retirement note).
- **Validation surface**: new skill must pass `claude plugin validate`, the marketplace policy
validator (scoped `allowed-tools`), and its `/skill-gate` benchmark; new shell must pass
`bash -n` + ShellCheck. No new dependencies, no new secrets.
Loading