Add frontend integration checklist and docs-drift guard
All checks were successful
CI / backend (push) Successful in 24s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s

PLUGIN-QUICKSTART Step 7 is now a concrete 7-item checklist (view
conventions, route auto-discovery + ADR-009 gating meta, api client
shape, nav/report hooks, settings auto-nesting, verification).

New tests/test_docs_contract.py introspects BasePlugin and fails CI when
a public hook is missing from PLUGIN-HOOKS.md or the documented contract
version drifts - it immediately caught two undocumented hooks
(get_provisioning_note, get_config_schema), now documented.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-07-11 10:14:25 -04:00
parent 54c4c808cc
commit 94f852a1c8
3 changed files with 136 additions and 6 deletions

View File

@@ -0,0 +1,61 @@
"""Docs-drift guards: docs/PLUGIN-HOOKS.md must track the live contract.
PLUGIN-HOOKS.md is the canonical plugin-author reference. These tests fail
when the contract surface moves without the doc: a version bump that skips
the doc's version example, or a new/renamed BasePlugin hook with no doc
mention. Keeping this structural (not manual review) is what lets sister
sites trust the doc.
"""
import inspect
from pathlib import Path
from shopdb import __contract_version__
from shopdb.plugins.base import BasePlugin
HOOKS_DOC = Path(__file__).resolve().parent.parent / 'docs' / 'PLUGIN-HOOKS.md'
def test_hooks_doc_exists():
assert HOOKS_DOC.exists(), 'docs/PLUGIN-HOOKS.md is missing'
def test_hooks_doc_declares_current_contract_version():
"""The doc's version example must match the live __contract_version__."""
text = HOOKS_DOC.read_text()
expected = f"__contract_version__ = '{__contract_version__}'"
assert expected in text, (
f'docs/PLUGIN-HOOKS.md version example is stale: expected {expected}. '
f'Update the "Contract version" section when bumping the contract.'
)
def test_every_public_hook_is_documented():
"""Every public BasePlugin method must be mentioned in the doc."""
text = HOOKS_DOC.read_text()
hooks = [
name for name, member in inspect.getmembers(
BasePlugin, predicate=inspect.isfunction)
if not name.startswith('_')
]
assert hooks, 'No public hooks found on BasePlugin (introspection broke?)'
missing = [hook for hook in hooks if hook not in text]
assert not missing, (
'BasePlugin hooks missing from docs/PLUGIN-HOOKS.md: '
+ ', '.join(missing)
+ '. Add a section (or mention) for each before shipping the hook.'
)
def test_doc_does_not_reference_removed_hooks():
"""Hooks removed from the contract must not be documented as current.
They may appear in "Removed" notes; this only guards section headings.
"""
text = HOOKS_DOC.read_text()
for removed in ('get_searchable_fields', 'get_event_handlers'):
assert not hasattr(BasePlugin, removed)
assert f'### `{removed}' not in text, (
f'{removed} was removed from the contract but still has a '
f'section heading in docs/PLUGIN-HOOKS.md'
)