From 2ee7fd5ab44eced01baa28e765ce74bf1a7869c9 Mon Sep 17 00:00:00 2001 From: SunsetDrifter Date: Sun, 26 Jul 2026 21:20:10 +0200 Subject: [PATCH] chore: replace variant fixtures with feature-named extension configs The retired variants' lint.py files under tests/fixtures/ existed only to keep engine knobs tested (membership, mermaid, staleness, index pinning, sync drift, reverse deps, ADRs, sharding, coverage). Replace them with plain feature-named config dicts in tests/extension_configs.py (infra / pinned-repo / sharded-repo) so nothing in the tree carries the old variant names. No feature or coverage loss; 91 tests green. --- tests/extension_configs.py | 380 ++++++++++++++++++++++++++ tests/fixtures/codebase-large/lint.py | 159 ----------- tests/fixtures/codebase/lint.py | 168 ------------ tests/fixtures/homelab/lint.py | 138 ---------- tests/helpers.py | 20 +- tests/test_checks.py | 26 +- tests/test_variants.py | 8 +- 7 files changed, 407 insertions(+), 492 deletions(-) create mode 100644 tests/extension_configs.py delete mode 100644 tests/fixtures/codebase-large/lint.py delete mode 100644 tests/fixtures/codebase/lint.py delete mode 100644 tests/fixtures/homelab/lint.py diff --git a/tests/extension_configs.py b/tests/extension_configs.py new file mode 100644 index 0000000..11a7963 --- /dev/null +++ b/tests/extension_configs.py @@ -0,0 +1,380 @@ +"""Config bundles exercising engine knobs the shipped template leaves at +their defaults: membership, mermaid diagrams, last_verified staleness, +index pinning + sync drift, reverse-dependency fields, ADRs, subsystem +sharding, and coverage reports. Feature-named test data, not templates; +values originate from retired variant configs (see git history).""" + + +def index_entry_extra(fields): + return f"(updated {fields.get('updated', '?')})" + + +# knobs: infra +INFRA = {'page_dirs': ['components', + 'topology', + 'concepts', + 'runbooks', + 'incidents', + 'synthesis', + 'queries', + 'sources'], + 'non_page_allowed': ['CLAUDE.md', + 'index.md', + 'log.md', + 'lint.py', + 'wikilint', + 'taxonomy.md', + 'raw', + 'workflows', + '.git', + '.gitignore', + '.githooks', + '.claude'], + 'skills_dir': '.claude/skills', + 'skills_prefix': 'wiki-', + 'raw_dir': 'raw', + 'inbox_dir': 'raw/inbox', + 'inbox_warn_count': 10, + 'inbox_warn_age_days': 14, + 'claude_md_max_lines': 200, + 'taxonomy_file': 'taxonomy.md', + 'contested_max_days': 30, + 'reverse_fields': ['depends_on'], + 'membership': {'member_type': 'component', + 'active_statuses': ['active'], + 'container_type': 'topology', + 'container_field': 'includes'}, + 'required_fields': ['type', 'created', 'updated', 'description', 'tags'], + 'type_required': {'component': ['component_kind', 'status', 'last_verified'], + 'topology': ['scope', 'includes'], + 'concept': [], + 'runbook': [], + 'incident': [], + 'synthesis': [], + 'source': [], + 'query': []}, + 'enum_fields': {'confidence': ['low', 'contested']}, + 'type_enum_fields': {'component': {'component_kind': ['host', + 'vm', + 'container', + 'service', + 'device', + 'subnet', + 'peer', + 'volume'], + 'status': ['active', + 'planned', + 'deprecated', + 'retired']}, + 'topology': {'scope': ['physical', + 'l2', + 'l3', + 'service', + 'trust', + 'data-flow']}}, + 'path_fields': ['sources', 'includes'], + 'edge_fields': ['depends_on'], + 'staleness': [{'field': 'last_verified', + 'types': ['component'], + 'max_days': 60, + 'severity': 'warning'}], + 'sync_drift': None, + 'criticality_field': None, + 'mermaid_required_types': ['topology'], + 'mermaid_node_warn': None, + 'mermaid_node_error': None, + 'adr_dirs': [], + 'index_mode': 'flat', + 'index_sections': [('Components', ['component']), + ('Topology', ['topology']), + ('Runbooks', ['runbook']), + ('Incidents', ['incident']), + ('Concepts', ['concept']), + ('Synthesis', ['synthesis']), + ('Sources', ['source']), + ('Queries', ['query'])], + 'owner_review_max_days': None, + 'coverage': False} + +# knobs: pinned repo +PINNED_REPO = {'page_dirs': ['modules', + 'services', + 'apis', + 'data-models', + 'architecture', + 'adrs', + 'concepts', + 'runbooks', + 'postmortems', + 'synthesis', + 'queries'], + 'non_page_allowed': ['CLAUDE.md', + 'index.md', + 'log.md', + 'lint.py', + 'wikilint', + 'taxonomy.md', + 'raw', + 'workflows', + '.git', + '.gitignore', + '.githooks', + '.claude'], + 'skills_dir': '.claude/skills', + 'skills_prefix': 'wiki-', + 'raw_dir': 'raw', + 'inbox_dir': 'raw/inbox', + 'inbox_warn_count': 10, + 'inbox_warn_age_days': 14, + 'claude_md_max_lines': 200, + 'taxonomy_file': 'taxonomy.md', + 'contested_max_days': 30, + 'reverse_fields': ['depends_on', + 'defined_in', + 'modules', + 'exposes', + 'consumes', + 'producers', + 'consumers'], + 'membership': {'member_type': 'module', + 'active_statuses': ['active'], + 'container_type': 'architecture', + 'container_field': 'includes'}, + 'required_fields': ['type', 'created', 'updated', 'description', 'tags'], + 'type_required': {'module': ['source_path', + 'language', + 'status', + 'last_verified_commit', + 'last_verified'], + 'service': ['source_path', + 'deployment', + 'last_verified_commit', + 'last_verified'], + 'api': ['api_kind', + 'defined_in', + 'stability', + 'last_verified_commit'], + 'data-model': ['model_kind', + 'defined_in', + 'storage', + 'last_verified_commit'], + 'architecture': ['scope', 'includes'], + 'adr': ['adr_number', 'status', 'date'], + 'concept': [], + 'runbook': [], + 'postmortem': [], + 'synthesis': [], + 'source': [], + 'query': []}, + 'enum_fields': {'confidence': ['low', 'contested']}, + 'type_enum_fields': {'module': {'status': ['active', + 'experimental', + 'deprecated', + 'removed']}, + 'api': {'api_kind': ['http', + 'grpc', + 'cli', + 'library', + 'event'], + 'stability': ['stable', + 'beta', + 'experimental', + 'deprecated']}, + 'data-model': {'model_kind': ['db-table', + 'type', + 'schema', + 'message']}, + 'architecture': {'scope': ['system', + 'service', + 'module', + 'data-flow', + 'deployment', + 'request-flow']}, + 'adr': {'status': ['proposed', + 'accepted', + 'superseded', + 'rejected']}}, + 'path_fields': ['sources', + 'includes', + 'defined_in', + 'modules', + 'exposes', + 'consumes', + 'producers', + 'consumers', + 'supersedes', + 'superseded_by'], + 'edge_fields': ['depends_on'], + 'staleness': [], + 'sync_drift': ('last_verified_commit', + ['module', 'service', 'api', 'data-model']), + 'criticality_field': None, + 'mermaid_required_types': ['architecture'], + 'mermaid_node_warn': None, + 'mermaid_node_error': None, + 'adr_dirs': ['adrs'], + 'index_mode': 'flat', + 'index_sections': [('Services', ['service']), + ('Modules', ['module']), + ('APIs', ['api']), + ('Data Models', ['data-model']), + ('Architecture', ['architecture']), + ('ADRs', ['adr']), + ('Runbooks', ['runbook']), + ('Postmortems', ['postmortem']), + ('Concepts', ['concept']), + ('Synthesis', ['synthesis']), + ('Queries', ['query'])], + 'owner_review_max_days': None, + 'coverage': False} + +# knobs: sharded repo +SHARDED_REPO = {'page_dirs': ['subsystems', + 'adrs', + 'architecture', + 'concepts', + 'runbooks', + 'postmortems', + 'synthesis', + 'queries'], + 'non_page_allowed': ['CLAUDE.md', + 'index.md', + 'log.md', + 'coverage.md', + 'glossary.md', + 'lint.py', + 'wikilint', + 'taxonomy.md', + 'raw', + 'workflows', + '.git', + '.gitignore', + '.githooks', + '.claude'], + 'skills_dir': '.claude/skills', + 'skills_prefix': 'wiki-', + 'raw_dir': 'raw', + 'inbox_dir': 'raw/inbox', + 'inbox_warn_count': 10, + 'inbox_warn_age_days': 14, + 'claude_md_max_lines': 200, + 'taxonomy_file': 'taxonomy.md', + 'contested_max_days': 30, + 'reverse_fields': ['depends_on', + 'defined_in', + 'modules', + 'exposes', + 'consumes', + 'producers', + 'consumers'], + 'membership': None, + 'required_fields': ['type', + 'created', + 'updated', + 'description', + 'tags', + 'subsystem'], + 'type_required': {'subsystem': ['owners', 'mission', 'last_owner_review'], + 'owners': [], + 'module': ['source_path', + 'language', + 'status', + 'criticality', + 'last_verified_commit', + 'last_verified', + 'verification_method'], + 'service': ['source_path', + 'deployment', + 'last_verified_commit', + 'last_verified'], + 'api': ['api_kind', + 'defined_in', + 'stability', + 'last_verified_commit'], + 'data-model': ['model_kind', + 'defined_in', + 'storage', + 'last_verified_commit'], + 'architecture': ['scope', 'includes'], + 'adr': ['adr_number', 'status', 'date'], + 'concept': [], + 'runbook': [], + 'postmortem': [], + 'synthesis': [], + 'source': [], + 'query': []}, + 'enum_fields': {'confidence': ['low', 'contested']}, + 'type_enum_fields': {'module': {'status': ['active', + 'experimental', + 'deprecated', + 'removed'], + 'criticality': ['load-bearing', + 'important', + 'normal', + 'peripheral'], + 'verification_method': ['full', + 'spot-check', + 'signature-only', + 'declared-by-owner']}, + 'api': {'api_kind': ['http', + 'grpc', + 'cli', + 'library', + 'event'], + 'stability': ['stable', + 'beta', + 'experimental', + 'deprecated']}, + 'data-model': {'model_kind': ['db-table', + 'type', + 'schema', + 'message']}, + 'architecture': {'scope': ['system', + 'service', + 'module', + 'data-flow', + 'deployment', + 'request-flow']}, + 'adr': {'status': ['proposed', + 'accepted', + 'superseded', + 'rejected']}}, + 'path_fields': ['sources', + 'includes', + 'defined_in', + 'modules', + 'exposes', + 'consumes', + 'producers', + 'consumers', + 'supersedes', + 'superseded_by', + 'key_modules', + 'key_services', + 'key_adrs'], + 'edge_fields': ['depends_on'], + 'staleness': [], + 'sync_drift': ('last_verified_commit', + ['module', 'service', 'api', 'data-model']), + 'criticality_field': 'criticality', + 'mermaid_required_types': ['architecture'], + 'mermaid_node_warn': 30, + 'mermaid_node_error': 50, + 'adr_dirs': ['adrs', 'subsystems/*/adrs'], + 'index_mode': 'subsystems', + 'index_sections': [('ADRs', ['adr']), + ('Architecture', ['architecture']), + ('Concepts', ['concept']), + ('Runbooks', ['runbook']), + ('Postmortems', ['postmortem']), + ('Synthesis', ['synthesis']), + ('Queries', ['query'])], + 'owner_review_max_days': 90, + 'coverage': True} + + +EXTENSION_CONFIGS = { + "infra": INFRA, + "pinned-repo": PINNED_REPO, + "sharded-repo": SHARDED_REPO, +} diff --git a/tests/fixtures/codebase-large/lint.py b/tests/fixtures/codebase-large/lint.py deleted file mode 100644 index 1728c5f..0000000 --- a/tests/fixtures/codebase-large/lint.py +++ /dev/null @@ -1,159 +0,0 @@ -#!/usr/bin/env python3 -"""Deterministic lint for this wiki (large codebase variant). Stdlib only; run from -the wiki root. Subcommands: check | rebuild-index | reverse-deps | coverage. - -This file holds ONLY the per-variant configuration. All logic lives in the -adjacent wikilint/ package, which is byte-identical across variants so fixes -propagate by copy. If a check can be a script, it belongs there, not in prose. -""" - -CONFIG = { - # Directories containing wiki pages, relative to the wiki root. - "page_dirs": [ - "subsystems", "adrs", "architecture", "concepts", "runbooks", - "postmortems", "synthesis", "queries", - ], - # Top-level entries that are allowed to exist but are not wiki pages. - "non_page_allowed": [ - "CLAUDE.md", "index.md", "log.md", "coverage.md", "glossary.md", - "lint.py", "wikilint", "taxonomy.md", "raw", "workflows", ".git", ".gitignore", ".githooks", ".claude", - ], - # Claude Code skill wrappers pair 1:1 with workflows/ (check_skills). - "skills_dir": ".claude/skills", - "skills_prefix": "wiki-", - "raw_dir": "raw", - "inbox_dir": "raw/inbox", - "inbox_warn_count": 10, - "inbox_warn_age_days": 14, - # Hot-core size guard: warn when CLAUDE.md exceeds this many lines. - "claude_md_max_lines": 200, - # Every page tag must appear in this file (one `- tag — meaning` line each). - "taxonomy_file": "taxonomy.md", - # Contested pages untouched for this many days get flagged for reconcile. - "contested_max_days": 30, - # Fields whose derived reverse maps `reverse-deps` prints. Together these - # cover every relationship the schema stores forward-only. - "reverse_fields": [ - "depends_on", "defined_in", "modules", "exposes", "consumes", - "producers", "consumers", - ], - # Architecture membership is per-subsystem judgment here (see lint.md - # "Subsystem orphans"); the mechanical check is disabled. - "membership": None, - # Frontmatter required on every page. - "required_fields": ["type", "created", "updated", "description", "tags", "subsystem"], - # type -> extra required fields for that type. - "type_required": { - "subsystem": ["owners", "mission", "last_owner_review"], - "owners": [], - "module": [ - "source_path", "language", "status", "criticality", - "last_verified_commit", "last_verified", "verification_method", - ], - "service": ["source_path", "deployment", "last_verified_commit", "last_verified"], - "api": ["api_kind", "defined_in", "stability", "last_verified_commit"], - "data-model": ["model_kind", "defined_in", "storage", "last_verified_commit"], - "architecture": ["scope", "includes"], - "adr": ["adr_number", "status", "date"], - "concept": [], - "runbook": [], - "postmortem": [], - "synthesis": [], - "source": [], - "query": [], - }, - # Optional fields with a fixed value set. `confidence` is absent on - # normal pages; only the two structurally checkable states exist. - "enum_fields": { - "confidence": ["low", "contested"], - }, - # Per-type enum fields: type -> {field: [values]}. - "type_enum_fields": { - "module": { - "status": ["active", "experimental", "deprecated", "removed"], - "criticality": ["load-bearing", "important", "normal", "peripheral"], - "verification_method": [ - "full", "spot-check", "signature-only", "declared-by-owner", - ], - }, - "api": { - "api_kind": ["http", "grpc", "cli", "library", "event"], - "stability": ["stable", "beta", "experimental", "deprecated"], - }, - "data-model": { - "model_kind": ["db-table", "type", "schema", "message"], - }, - "architecture": { - "scope": ["system", "service", "module", "data-flow", "deployment", "request-flow"], - }, - "adr": { - "status": ["proposed", "accepted", "superseded", "rejected"], - }, - }, - # Frontmatter fields whose values are wiki paths that must exist. - "path_fields": [ - "sources", "includes", "defined_in", "modules", "exposes", "consumes", - "producers", "consumers", "supersedes", "superseded_by", - "key_modules", "key_services", "key_adrs", - ], - # Fields that define dependency edges (stored once, on the depender). - # The reverse map is derived, never stored. - "edge_fields": ["depends_on"], - # Staleness rules: each dict flags pages of the given types whose date - # field is older than max_days. severity: "error" | "warning". - "staleness": [], - # Compare this commit field against `last_synced_commit` in the index - # pinning block. None disables. Format: (field, [types]) or None. - "sync_drift": ("last_verified_commit", ["module", "service", "api", "data-model"]), - # Criticality field for weighted drift reporting (large variant). None - # disables weighting. - "criticality_field": "criticality", - # Page types that must contain at least one ```mermaid block. - "mermaid_required_types": ["architecture"], - "mermaid_node_warn": 30, - "mermaid_node_error": 50, - # Directories holding numbered ADRs (NNNN-slug.md). Empty disables. - "adr_dirs": ["adrs", "subsystems/*/adrs"], - # Index generation. Subsystem mode lists subsystem READMEs first, then - # only pages with `subsystem: global` in the sections below. - "index_mode": "subsystems", # "flat" | "subsystems" - "index_sections": [ - ("ADRs", ["adr"]), - ("Architecture", ["architecture"]), - ("Concepts", ["concept"]), - ("Runbooks", ["runbook"]), - ("Postmortems", ["postmortem"]), - ("Synthesis", ["synthesis"]), - ("Queries", ["query"]), - ], - # Owner review staleness in days (large variant). None disables. - "owner_review_max_days": 90, - # Coverage report (large variant only). - "coverage": True, - # Engine extension points (okf_conformance, orphans, index_file, - # index_body_fn, iso_date_fields, extra_secret_patterns, secret_allow_res, - # log_file, extra_checks) default to original wiki behavior in - # wikilint/settings.py DEFAULTS. Add a key here only to override one, e.g. - # to lint a non-wiki markdown tree. -} - - -def index_entry_extra(fields): - """Trailing annotation for an index entry, by page type.""" - ptype = fields.get("type") - if ptype == "module": - status = fields.get("status", "?") - criticality = fields.get("criticality", "?") - verified = fields.get("last_verified_commit", "?") - return f"({status}/{criticality}, verified {verified})" - if ptype in ("service", "api", "data-model"): - return f"(verified {fields.get('last_verified_commit', '?')})" - if ptype == "adr": - return f"({fields.get('status', '?')} {fields.get('date', '?')})" - return f"(updated {fields.get('updated', '?')})" - - -if __name__ == "__main__": - import sys - from wikilint import main - sys.exit(main(CONFIG, index_entry_extra)) diff --git a/tests/fixtures/codebase/lint.py b/tests/fixtures/codebase/lint.py deleted file mode 100644 index 0c23116..0000000 --- a/tests/fixtures/codebase/lint.py +++ /dev/null @@ -1,168 +0,0 @@ -#!/usr/bin/env python3 -"""Deterministic lint for this wiki (codebase variant). Stdlib only; run from -the wiki root. Subcommands: check | rebuild-index | reverse-deps | coverage. - -This file holds ONLY the per-variant configuration. All logic lives in the -adjacent wikilint/ package, which is byte-identical across variants so fixes -propagate by copy. If a check can be a script, it belongs there, not in prose. -""" - -CONFIG = { - # Directories containing wiki pages, relative to the wiki root. - "page_dirs": [ - "modules", "services", "apis", "data-models", "architecture", - "adrs", "concepts", "runbooks", "postmortems", "synthesis", "queries", - ], - # Top-level entries that are allowed to exist but are not wiki pages. - "non_page_allowed": [ - "CLAUDE.md", "index.md", "log.md", "lint.py", "wikilint", "taxonomy.md", "raw", "workflows", - ".git", ".gitignore", ".githooks", ".claude", - ], - # Claude Code skill wrappers pair 1:1 with workflows/ (check_skills). - "skills_dir": ".claude/skills", - "skills_prefix": "wiki-", - "raw_dir": "raw", - "inbox_dir": "raw/inbox", - "inbox_warn_count": 10, - "inbox_warn_age_days": 14, - # Hot-core size guard: warn when CLAUDE.md exceeds this many lines. - "claude_md_max_lines": 200, - # Every page tag must appear in this file (one `- tag — meaning` line each). - "taxonomy_file": "taxonomy.md", - # Contested pages untouched for this many days get flagged for reconcile. - "contested_max_days": 30, - # Fields whose derived reverse maps `reverse-deps` prints. Together these - # cover every relationship the schema stores forward-only. - "reverse_fields": [ - "depends_on", "defined_in", "modules", "exposes", "consumes", - "producers", "consumers", - ], - # Every active module must appear in at least one architecture page. - "membership": { - "member_type": "module", - "active_statuses": ["active"], - "container_type": "architecture", - "container_field": "includes", - }, - # Frontmatter required on every page. - "required_fields": ["type", "created", "updated", "description", "tags"], - # type -> extra required fields for that type. - "type_required": { - "module": [ - "source_path", "language", "status", - "last_verified_commit", "last_verified", - ], - "service": [ - "source_path", "deployment", - "last_verified_commit", "last_verified", - ], - "api": ["api_kind", "defined_in", "stability", "last_verified_commit"], - "data-model": ["model_kind", "defined_in", "storage", "last_verified_commit"], - "architecture": ["scope", "includes"], - "adr": ["adr_number", "status", "date"], - "concept": [], - "runbook": [], - "postmortem": [], - "synthesis": [], - "source": [], - "query": [], - }, - # Optional fields with a fixed value set. `confidence` is absent on - # normal pages; only the two structurally checkable states exist. - "enum_fields": { - "confidence": ["low", "contested"], - }, - # Per-type enum fields: type -> {field: [values]}. - "type_enum_fields": { - "module": { - "status": ["active", "experimental", "deprecated", "removed"], - }, - "api": { - "api_kind": ["http", "grpc", "cli", "library", "event"], - "stability": ["stable", "beta", "experimental", "deprecated"], - }, - "data-model": { - "model_kind": ["db-table", "type", "schema", "message"], - }, - "architecture": { - "scope": [ - "system", "service", "module", - "data-flow", "deployment", "request-flow", - ], - }, - "adr": { - "status": ["proposed", "accepted", "superseded", "rejected"], - }, - }, - # Frontmatter fields whose values are wiki paths that must exist. - "path_fields": [ - "sources", "includes", "defined_in", "modules", "exposes", - "consumes", "producers", "consumers", "supersedes", "superseded_by", - ], - # Fields that define dependency edges (stored once, on the depender). - # The reverse map is derived, never stored. - "edge_fields": ["depends_on"], - # Staleness rules: each dict flags pages of the given types whose date - # field is older than max_days. severity: "error" | "warning". - "staleness": [], - # Compare this commit field against `last_synced_commit` in the index - # pinning block. None disables. Format: (field, [types]) or None. - "sync_drift": ("last_verified_commit", ["module", "service", "api", "data-model"]), - # Criticality field for weighted drift reporting (large variant). None - # disables weighting. - "criticality_field": None, - # Page types that must contain at least one ```mermaid block. - "mermaid_required_types": ["architecture"], - "mermaid_node_warn": None, - "mermaid_node_error": None, - # Directories holding numbered ADRs (NNNN-slug.md). Empty disables. - "adr_dirs": ["adrs"], - # Index generation. - "index_mode": "flat", # "flat" | "subsystems" - "index_sections": [ - ("Services", ["service"]), - ("Modules", ["module"]), - ("APIs", ["api"]), - ("Data Models", ["data-model"]), - ("Architecture", ["architecture"]), - ("ADRs", ["adr"]), - ("Runbooks", ["runbook"]), - ("Postmortems", ["postmortem"]), - ("Concepts", ["concept"]), - ("Synthesis", ["synthesis"]), - ("Queries", ["query"]), - ], - # Owner review staleness in days (large variant). None disables. - "owner_review_max_days": None, - # Coverage report (large variant only). - "coverage": False, - # Engine extension points (okf_conformance, orphans, index_file, - # index_body_fn, iso_date_fields, extra_secret_patterns, secret_allow_res, - # log_file, extra_checks) default to original wiki behavior in - # wikilint/settings.py DEFAULTS. Add a key here only to override one, e.g. - # to lint a non-wiki markdown tree. -} - - -def index_entry_extra(fields): - """Trailing annotation for an index entry, by page type.""" - ptype = fields.get("type") - if ptype == "module": - status = fields.get("status", "?") - commit = fields.get("last_verified_commit", "?") - return f"({status}, verified {commit})" - if ptype in ("service", "api", "data-model"): - commit = fields.get("last_verified_commit", "?") - return f"(verified {commit})" - if ptype == "adr": - status = fields.get("status", "?") - adr_date = fields.get("date", "?") - return f"({status} {adr_date})" - updated = fields.get("updated", "?") - return f"(updated {updated})" - - -if __name__ == "__main__": - import sys - from wikilint import main - sys.exit(main(CONFIG, index_entry_extra)) diff --git a/tests/fixtures/homelab/lint.py b/tests/fixtures/homelab/lint.py deleted file mode 100644 index 6321e06..0000000 --- a/tests/fixtures/homelab/lint.py +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env python3 -"""Deterministic lint for this wiki (homelab variant). Stdlib only; run from -the wiki root. Subcommands: check | rebuild-index | reverse-deps | coverage. - -This file holds ONLY the per-variant configuration. All logic lives in the -adjacent wikilint/ package, which is byte-identical across variants so fixes -propagate by copy. If a check can be a script, it belongs there, not in prose. -""" - -CONFIG = { - # Directories containing wiki pages, relative to the wiki root. - "page_dirs": [ - "components", "topology", "concepts", "runbooks", "incidents", - "synthesis", "queries", "sources", - ], - # Top-level entries that are allowed to exist but are not wiki pages. - "non_page_allowed": [ - "CLAUDE.md", "index.md", "log.md", "lint.py", "wikilint", "taxonomy.md", "raw", "workflows", - ".git", ".gitignore", ".githooks", ".claude", - ], - # Claude Code skill wrappers pair 1:1 with workflows/ (check_skills). - "skills_dir": ".claude/skills", - "skills_prefix": "wiki-", - "raw_dir": "raw", - "inbox_dir": "raw/inbox", - "inbox_warn_count": 10, - "inbox_warn_age_days": 14, - # Hot-core size guard: warn when CLAUDE.md exceeds this many lines. - "claude_md_max_lines": 200, - # Every page tag must appear in this file (one `- tag — meaning` line each). - "taxonomy_file": "taxonomy.md", - # Contested pages untouched for this many days get flagged for reconcile. - "contested_max_days": 30, - # Fields whose derived reverse maps `reverse-deps` prints. - "reverse_fields": ["depends_on"], - # Every active component must appear in at least one topology page. - "membership": { - "member_type": "component", - "active_statuses": ["active"], - "container_type": "topology", - "container_field": "includes", - }, - # Frontmatter required on every page. - "required_fields": ["type", "created", "updated", "description", "tags"], - # type -> extra required fields for that type. - "type_required": { - "component": ["component_kind", "status", "last_verified"], - "topology": ["scope", "includes"], - "concept": [], - "runbook": [], - "incident": [], - "synthesis": [], - "source": [], - "query": [], - }, - # Optional fields with a fixed value set. `confidence` is absent on - # normal pages; only the two structurally checkable states exist. - "enum_fields": { - "confidence": ["low", "contested"], - }, - # Per-type enum fields: type -> {field: [values]}. - "type_enum_fields": { - "component": { - "component_kind": [ - "host", "vm", "container", "service", "device", "subnet", - "peer", "volume", - ], - "status": ["active", "planned", "deprecated", "retired"], - }, - "topology": { - "scope": ["physical", "l2", "l3", "service", "trust", "data-flow"], - }, - }, - # Frontmatter fields whose values are wiki paths that must exist. - "path_fields": ["sources", "includes"], - # Fields that define dependency edges (stored once, on the depender). - # The reverse map is derived, never stored. - "edge_fields": ["depends_on"], - # Staleness rules: each dict flags pages of the given types whose date - # field is older than max_days. severity: "error" | "warning". - "staleness": [ - { - "field": "last_verified", - "types": ["component"], - "max_days": 60, - "severity": "warning", - }, - ], - # Compare this commit field against `last_synced_commit` in the index - # pinning block. None disables. Format: (field, [types]) or None. - "sync_drift": None, - # Criticality field for weighted drift reporting (large variant). None - # disables weighting. - "criticality_field": None, - # Page types that must contain at least one ```mermaid block. - "mermaid_required_types": ["topology"], - "mermaid_node_warn": None, - "mermaid_node_error": None, - # Directories holding numbered ADRs (NNNN-slug.md). Empty disables. - "adr_dirs": [], - # Index generation. - "index_mode": "flat", # "flat" | "subsystems" - "index_sections": [ - ("Components", ["component"]), - ("Topology", ["topology"]), - ("Runbooks", ["runbook"]), - ("Incidents", ["incident"]), - ("Concepts", ["concept"]), - ("Synthesis", ["synthesis"]), - ("Sources", ["source"]), - ("Queries", ["query"]), - ], - # Owner review staleness in days (large variant). None disables. - "owner_review_max_days": None, - # Coverage report (large variant only). - "coverage": False, - # Engine extension points (okf_conformance, orphans, index_file, - # index_body_fn, iso_date_fields, extra_secret_patterns, secret_allow_res, - # log_file, extra_checks) default to original wiki behavior in - # wikilint/settings.py DEFAULTS. Add a key here only to override one, e.g. - # to lint a non-wiki markdown tree. -} - - -def index_entry_extra(fields): - """Trailing annotation for an index entry, by page type.""" - if fields.get("type") == "component": - status = fields.get("status", "?") - last_verified = fields.get("last_verified", "?") - return f"({status}, verified {last_verified})" - updated = fields.get("updated", "?") - return f"(updated {updated})" - - -if __name__ == "__main__": - import sys - from wikilint import main - sys.exit(main(CONFIG, index_entry_extra)) diff --git a/tests/helpers.py b/tests/helpers.py index 3dca455..873e9c8 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -9,6 +9,8 @@ from wikilint.settings import configure # noqa: E402 +import extension_configs # noqa: E402 + VARIANTS = ["wiki"] TODAY = date.today().isoformat() @@ -17,17 +19,15 @@ def load_variant_config(variant): - """Execute a variant's lint.py head (the __main__ guard keeps it inert). - - Besides the shipped template ("wiki"), configs of the retired variants - live on under tests/fixtures/ purely as engine coverage: they exercise - knobs (membership, staleness, sync_drift, coverage) no shipped template - currently sets.""" - path = REPO / variant / "lint.py" - if not path.is_file(): - path = REPO / "tests" / "fixtures" / variant / "lint.py" + """Load a config under test: the shipped template's lint.py head (the + __main__ guard keeps it inert), or a feature-named bundle from + extension_configs.py exercising engine knobs the template leaves at + their defaults.""" + if variant in extension_configs.EXTENSION_CONFIGS: + return (dict(extension_configs.EXTENSION_CONFIGS[variant]), + extension_configs.index_entry_extra) ns = {"__name__": "lint_config_under_test"} - exec(compile(path.read_text(), "lint.py", "exec"), ns) + exec(compile((REPO / variant / "lint.py").read_text(), "lint.py", "exec"), ns) return ns["CONFIG"], ns["index_entry_extra"] diff --git a/tests/test_checks.py b/tests/test_checks.py index e627a1b..6a48a04 100644 --- a/tests/test_checks.py +++ b/tests/test_checks.py @@ -62,8 +62,8 @@ class TestBodyExtraction(WikiTest): def test_trailing_space_delimiter_keeps_body(self): # Review regression: "--- " closing line used to empty the body, # silently skipping body checks and false-erroring mermaid pages. - use_variant("homelab") - root = make_wiki(self.tmp, variant="homelab", files={ + use_variant("infra") + root = make_wiki(self.tmp, variant="infra", files={ "topology/lan.md": ( "---\ntype: topology\ncreated: 2026-07-01\nupdated: 2026-07-01\n" "description: LAN map.\ntags: [alpha]\nsources: []\nscope: l2\n" @@ -122,7 +122,7 @@ def test_active_component_outside_topology_flagged(self): component = page("component", "A switch.", tags="[alpha]", extra_fm="component_kind: device\nstatus: active\n" f"last_verified: {TODAY}\ndepends_on: []\n") - root = make_wiki(self.tmp, variant="homelab", files={ + root = make_wiki(self.tmp, variant="infra", files={ "components/switch-01.md": component, "components/router-01.md": component.replace("A switch.", "A router linking [[switch-01]]."), }) @@ -131,7 +131,7 @@ def test_active_component_outside_topology_flagged(self): self.assertIn("components/switch-01.md", flagged) def test_included_component_passes(self): - root = make_wiki(self.tmp, variant="homelab", files={ + root = make_wiki(self.tmp, variant="infra", files={ "components/switch-01.md": page( "component", "A switch.", extra_fm="component_kind: device\nstatus: active\n" @@ -234,8 +234,8 @@ def _index_head(self): def test_pinning_quotes_block_lists_and_decoy_fence(self): # Review regression: quoted commits, block lists, and an earlier yaml # fence all silently corrupted the pinning data. - use_variant("codebase") - root = make_wiki(self.tmp, variant="codebase") + use_variant("pinned-repo") + root = make_wiki(self.tmp, variant="pinned-repo") (root / "index.md").write_text(self._index_head()) from wikilint.model import parse_index_pinning pinning = parse_index_pinning(root) @@ -248,7 +248,7 @@ def test_sync_drift_uses_unquoted_commit(self): extra_fm="source_path: libs/auth/\nlanguage: go\nstatus: active\n" "depends_on: []\nlast_verified_commit: e4f5g6h\n" f"last_verified: {TODAY}\n") - root = make_wiki(self.tmp, variant="codebase", + root = make_wiki(self.tmp, variant="pinned-repo", files={"modules/auth.md": module}) (root / "index.md").write_text(self._index_head()) report = gather(root) @@ -259,12 +259,12 @@ def test_sync_drift_uses_unquoted_commit(self): class TestReverseDeps(WikiTest): def test_all_relationship_fields_reversed(self): # Review regression: only depends_on used to be derivable. - use_variant("codebase") + use_variant("pinned-repo") from wikilint.settings import CONFIG for field in ("defined_in", "modules", "exposes", "consumes", "producers", "consumers", "depends_on"): self.assertIn(field, CONFIG["reverse_fields"]) - root = make_wiki(self.tmp, variant="codebase", files={ + root = make_wiki(self.tmp, variant="pinned-repo", files={ "modules/auth.md": page( "module", "Auth.", extra_fm="source_path: libs/auth/\nlanguage: go\n" f"status: active\ndepends_on: []\nlast_verified_commit: abc\nlast_verified: {TODAY}\n"), @@ -304,7 +304,7 @@ def test_adr_gap_and_missing_forward_link(self): extra_fm="adr_number: 0001\nstatus: superseded\ndate: 2026-07-01\n") adr3 = page("adr", "Choose sqlite.", tags="[alpha]", extra_fm="adr_number: 0003\nstatus: accepted\ndate: 2026-07-02\n") - root = make_wiki(self.tmp, variant="codebase", files={ + root = make_wiki(self.tmp, variant="pinned-repo", files={ "adrs/0001-choose-postgres.md": adr, "adrs/0003-choose-sqlite.md": adr3, }) @@ -313,7 +313,7 @@ def test_adr_gap_and_missing_forward_link(self): self.assertEqual(len(findings(report, "adr", "WARNING")), 1) # gap 0002 def test_last_verified_staleness(self): - root = make_wiki(self.tmp, variant="homelab", files={ + root = make_wiki(self.tmp, variant="infra", files={ "components/old-box.md": page( "component", "Old box.", extra_fm="component_kind: host\nstatus: active\n" @@ -326,7 +326,7 @@ def test_last_verified_staleness(self): class TestCoverage(WikiTest): def test_coverage_anchored_and_grouped_by_subsystem(self): # Review regressions: 'src' used to cover 'src-utils'; no subsystem view. - use_variant("codebase-large") + use_variant("sharded-repo") repo = Path(self.tmp) / "fakerepo" (repo / "libs" / "auth").mkdir(parents=True) (repo / "libs" / "auth-extra").mkdir(parents=True) @@ -338,7 +338,7 @@ def test_coverage_anchored_and_grouped_by_subsystem(self): "status: active\ncriticality: load-bearing\ndepends_on: []\n" f"last_verified_commit: abc\nlast_verified: {TODAY}\n" "verification_method: full\n") - root = make_wiki(str(wiki), variant="codebase-large", + root = make_wiki(str(wiki), variant="sharded-repo", files={"subsystems/auth/modules/auth.md": module}) (root / "index.md").write_text( f"```yaml\nrepo: x\nlocal_path: {repo}\nlast_synced_commit: abc\n" diff --git a/tests/test_variants.py b/tests/test_variants.py index 718410f..c8a28a2 100644 --- a/tests/test_variants.py +++ b/tests/test_variants.py @@ -1,14 +1,14 @@ """Template invariants: compilable heads, sane configs, line budgets, and -hook correctness properties, for the shipped template and the retired-variant -configs kept under tests/fixtures/ as engine coverage.""" +hook correctness properties, for the shipped template and the feature-named +extension configs in extension_configs.py.""" import py_compile import unittest from helpers import REPO, VARIANTS, load_variant_config -# Shipped template plus config-only fixtures (see helpers.load_variant_config). -ALL_CONFIGS = VARIANTS + ["homelab", "codebase", "codebase-large"] +# Shipped template plus extension-knob config bundles. +ALL_CONFIGS = VARIANTS + ["infra", "pinned-repo", "sharded-repo"] class TestEngineIdentity(unittest.TestCase):