All notable changes to Zenzic are documented here. Format follows Keep a Changelog. Versions follow Semantic Versioning.
- Standalone Engine replaces Vanilla (Direttiva 037). The
VanillaAdapterand theengine = "vanilla"keyword have been removed. All projects must migrate toengine = "standalone". Anyzenzic.tomlstill usingengine = "vanilla"will raise aConfigurationError [Z000]at startup with a clear migration message. Migration: replaceengine = "vanilla"withengine = "standalone"in yourzenzic.tomlor[tool.zenzic]block.
-
Finding Codes (Zxxx) (Direttiva 036). Every diagnostic emitted by Zenzic now carries a unique machine-readable identifier (e.g.
Z101 LINK_BROKEN,Z201 SHIELD_SECRET,Z401 MISSING_DIRECTORY_INDEX). The full registry lives insrc/zenzic/core/codes.pyβ the single source of truth for all codes. -
Interactive Lab menu.
zenzic labwithout arguments now displays the act index so you can choose which scenario to explore. Runzenzic lab <N>to execute a specific act (0β8). The--actoption has been replaced by a positional argument. -
Standalone Mode identity.
StandaloneAdapteris the canonical no-op engine for pure Markdown projects.zenzic initnow writesengine = "standalone"when no framework config is detected. -
--offlineflag for Flat URL resolution. Available oncheck all,check links, andcheck orphans. Forces all adapters to produce.htmlURLs (e.g.guide/install.mdβ/guide/install.html) instead of directory-style slugs. -
Docusaurus v3 Multi-version support. The
DocusaurusAdapternow identifiesversions.json,versioned_docs/, and versioned translations. -
Zensical Transparent Proxy. If
engine = "zensical"is declared butzensical.tomlis missing, the adapter automatically bridges your existingmkdocs.yml. -
Version-aware Ghost Routing. Versioned documentation paths are automatically classified as
REACHABLE. -
@site/ Alias Resolution. Added support for the
@site/path alias inDocusaurusAdapter, enabling project-relative links to be resolved correctly. -
Directory Index Integrity. New
provides_index(path)method on theBaseAdapterprotocol enables engine-aware detection of directories that lack a landing page. TheMISSING_DIRECTORY_INDEXfinding (severity:info) is emitted byzenzic check allfor every subdirectory that contains Markdown sources but no engine-provided index entry β preventing hierarchical 404s before deployment. -
Sentinel Banner Notifications. New status messages for Offline Mode and Proxy Mode activation.
-
Guardians Audit: Official Specs Alignment.
- Docusaurus Versioning: Fixed "latest" version (first entry in
versions.json) URL mapping to exclude the version label prefix, matching official Docusaurus behavior. Previously every versioned file received a/version/prefix, causing false positive broken-link reports for all latest-version pages. - Docusaurus Slugs: Absolute frontmatter slugs (e.g.
slug: /my-path) are now correctly prepended withrouteBasePath(e.g./docs/my-path/), aligning with the DocusaurusnormalizeUrl([versionMetadata.path, docSlug])specification. - Smart File Collapsing:
isCategoryIndexlogic now mirrors Docusaurus exactly:README.md,INDEX.md(case-insensitive), and{FolderName}/{FolderName}.mdcollapse to the parent directory URL, preventing false positive broken-link reports for valid category landing page conventions. @site/Alias Resolution: TheInMemoryPathResolvernow resolves@site/links against the correctrepo_rootboundary instead of escaping via../, eliminating spuriousPathTraversalerrors for all Docusaurus project-relative links.
- Docusaurus Versioning: Fixed "latest" version (first entry in
-
Metadata Integrity. Corrected version string alignment in
CITATION.cffandpyproject.toml. -
Docusaurus routeBasePath default. Restored
docsas the default URL prefix for Docusaurus projects to match official engine behavior. -
Bilingual Documentation Parity. Full EN/IT documentation coverage for all v0.6.1 features across the Architecture, Engine, and Command guides.
- Shield: Unicode format character bypass (ZRT-006). Zero-width Unicode characters (ZWJ U+200D, ZWNJ U+200C, ZWSP U+200B) inserted mid-token could break regex matching. The normalizer now strips all Unicode category Cf characters before scanning.
- Shield: HTML entity obfuscation bypass (ZRT-006). HTML character
references (
AKβAK) could hide credential prefixes. The normalizer now decodes&#NNN;/&#xHH;entities viahtml.unescape(). - Shield: comment-interleaving bypass (ZRT-007). HTML comments
(
<!-- -->) and MDX comments ({/* */}) inserted mid-token could break pattern matching. The normalizer now strips both comment forms. - Shield: cross-line split-token detection (ZRT-007). Added a 1-line
lookback buffer via
scan_lines_with_lookback()to detect secrets split across two consecutive lines (e.g. YAML folded scalars). Suppresses duplicates via previous-line seen set.
--format jsonon individual check commands.check links,check orphans,check snippets,check references, andcheck assetsnow accept--format jsonwith a uniformfindings/summaryschema. Exit codes are preserved in JSON mode. (#55 β contributed by @xyaz1313)- Shield: GitLab Personal Access Token detection. The credential scanner now
detects
glpat-tokens (9 credential families total). (#57 β contributed by @gtanb4l)
- JSON exit-code asymmetry in
check orphansandcheck assets. Both commands now distinguisherrorvswarningseverity before deciding exit codes, consistent withcheck referencesandcheck snippets. Previously, any finding (including warnings) triggered Exit 1 in JSON mode.
- Removed
zenzic servecommand. Zenzic is now 100% subprocess-free, focusing exclusively on static source analysis. To preview your documentation, use your engine's native command:mkdocs serve,docusaurus start, orzensical serve. This removal eliminates the sole exception to Pillar 2 (No Subprocess) and completes the architectural purity of the framework. - MkDocs plugin moved to
zenzic.integrations.mkdocsβ Previously atzenzic.plugin. Update your MkDocsmkdocs.ymlto reinstall the package; the plugin is now auto-discovered via themkdocs.pluginsentry point. Requirespip install "zenzic[mkdocs]".
- Layered Exclusion Manager β New 4-level exclusion hierarchy (System
Guardrails > Forced Inclusions + VCS > Config > CLI). Pure-Python gitignore
parser (
VCSIgnoreParser) with pre-compiled regex patterns. New config fields:respect_vcs_ignore,included_dirs,included_file_patterns. - Universal Discovery β Zero
rglobcalls in the codebase. All file iteration flows throughwalk_files/iter_markdown_sourcesfromdiscovery.py. Mandatoryexclusion_managerparameter on all scanner and validator entry points β no Optional, no fallbacks. - CLI Exclusion Flags β
--exclude-dirand--include-dirrepeatable options on all check commands,score, anddiff. - Adapter Cache β Module-level cache keyed by
(engine, docs_root, repo_root). Single adapter instantiation per CLI session. - F4-1 Jailbreak Protection β
_validate_docs_root()rejectsdocs_dirpaths that escape the repository root (Blood Sentinel Exit 3). - F2-1 Shield Hardening β Lines exceeding 1 MiB are silently truncated before regex matching to prevent ReDoS.
zenzic.integrationsnamespace β MkDocs plugin relocated fromzenzic.plugintozenzic.integrations.mkdocs. Registered as an officialmkdocs.pluginsentry point. Core is now free of any engine-specific imports. Install the extra:pip install "zenzic[mkdocs]".
- BREAKING (Alpha):
exclusion_managerparameter is now mandatory onwalk_files,iter_markdown_sources,generate_virtual_site_map,check_nav_contract, and all scanner functions. No backward-compatibleNonedefault.
0.6.0a2 β 2026-04-13 β Obsidian Glass (Alpha 2)
- Glob Pattern Support for
excluded_assetsβexcluded_assetsentries are now matched viafnmatch(glob syntax:*,?,[],**). Literal paths continue to work as before. This bringsexcluded_assetsin line withexcluded_build_artifactsandexcluded_file_patterns, giving the entire exclusion API a single, consistent language. base_urlin[build_context]β New optional field that lets users declare the siteβs base URL explicitly. When set, the Docusaurus adapter skips static extraction fromdocusaurus.config.ts, eliminating the βdynamic patternsβ fallback warning for configs that useasync,import(), orrequire().- Metadata-Driven Routing β New
RouteMetadatadataclass andget_route_info()method on theBaseAdapterprotocol. All four adapters (Vanilla, MkDocs, Docusaurus, Zensical) implement the new API.build_vsm()prefers the metadata-driven path when available, falling back to the legacymap_url()+classify_route()pair for third-party adapters. - Centralized Frontmatter Extraction β Engine-agnostic utilities in
_utils.py:extract_frontmatter_slug(),extract_frontmatter_draft(),extract_frontmatter_unlisted(),extract_frontmatter_tags(), andbuild_metadata_cache()for single-pass eager harvesting of YAML frontmatter across all Markdown files. FileMetadatadataclass β Structured representation of per-file frontmatter:slug,draft,unlisted,tags.- Shield IO Middleware β
safe_read_line()scans every frontmatter line through the Shield before any parser sees it.ShieldViolationexception provides structured error withSecurityFindingpayload. - Protocol Compliance Tests β 43 new tests in
test_protocol_evolution.py:runtime_checkableprotocol validation,RouteMetadatainvariants,get_route_info()contract tests for all adapters, Hypothesis stress tests with extreme paths, pickle safety, frontmatter extraction, Shield middleware, and VanillaAdapter warning-free operation.
- BREAKING:
excluded_assetsuses fnmatch β All entries are now interpreted as glob patterns. Plain literal paths still match (they are valid patterns), but patterns like**/_category_.jsonorassets/brand/*are now supported natively. The previous set-subtraction implementation has been removed.
- Docusaurus βdynamic patternsβ warning emitted twice β When
base_urlis declared inzenzic.toml, the adapter no longer calls_extract_base_url(), suppressing the duplicate warning entirely.
0.6.0a1 β 2026-04-12 β Obsidian Glass
Alpha 1 of the v0.6 series. Zenzic evolves from a MkDocs-aware linter into an Analyser of Documentation Platforms. This release introduces the Docusaurus v3 engine adapter β the first non-MkDocs/Zensical adapter β and marks the beginning of the Obsidian Bridge migration strategy.
- Docusaurus v3 Adapter (Full Spec): New engine-agnostic adapter with
static-AST-like parsing for
docusaurus.config.ts/js. Satisfies theBaseAdapterprotocol. Handles.mdand.mdxsource files, auto-generated sidebar mode (all filesREACHABLE), Docusaurus i18n geography (i18n/{locale}/docusaurus-plugin-content-docs/current/), Ghost Route detection for locale index pages, and_-prefixed file/directory exclusion (IGNORED). Registered as built-in adapter with entry-pointdocusaurus = "zenzic.core.adapters:DocusaurusAdapter".baseUrlextraction: Multi-pattern static parser supportingexport default,module.exports, andconst/letassignment patterns. JS/TS comments are stripped before extraction. No Node.js subprocess (Pillar 2 compliance).routeBasePathextraction: Automatic detection ofrouteBasePathfrom Docusaurus presets and plugin blocks (e.g.@docusaurus/preset-classic). Supports empty string (docs at site root).- Slug Support: Markdown frontmatter
slug:overrides are now correctly mapped into the VSM. Absolute slugs (/custom-path) replace the full URL; relative slugs replace the last path segment. - Dynamic Config Detection: Intelligent detection of async config
creators,
import()/require()calls, and function-based exports. Falls back tobaseUrl='/'with a user warning β never crashes, never guesses.
from_repo()factory hook forDocusaurusAdapter: Auto-discoversdocusaurus.config.tsor.jsand constructs the adapter with the correctbaseUrlandrouteBasePath.- Improved i18n Topology: Native mapping for Docusaurus
i18n/directory structure and locale-specific route resolution.
tests/test_docusaurus_adapter.pyβ 65 tests across 12 test classes. Full coverage of the Docusaurus adapter refactor: config parsing (CFG-01..07),routeBasePathextraction (RBP-01), frontmatter slug support (SLUG-01), dynamic config detection, comment stripping,from_repo()integration, URL mapping regression, and route classification regression.
0.5.0a5 β 2026-04-09 β The Sentinel Codex
Alpha 5 Release. Visual-language overhaul: Sentinel Style Guide, card-grid refactoring, admonition/icon normalisation, 102 strategic anchor IDs, CSS card hover effects, and fully automated screenshot generation pipeline. Legacy PDF template removed. Changelog tracking stabilised. E2E CLI security tests added;
--exit-zerobug fixed (exits 2/3 are now unconditionally non-suppressible, matching the documented contract).
-
Sentinel Style Guide β canonical visual-language reference (
docs/internal/style-guide-sentinel.md+ Italian mirror) defining card grids, admonition types, icon vocabulary, and anchor-ID conventions. -
Automated screenshot generation β Blood & Circular SVGs.
scripts/generate_docs_assets.pynow generates all five documentation screenshots:screenshot.svg,screenshot-hero.svg,screenshot-score.svg,screenshot-blood.svg, andscreenshot-circular.svg. The Blood Sentinel and Circular Link SVGs were previously hand-crafted static assets; they are now deterministically generated from dedicated sandbox fixtures (tests/sandboxes/screenshot_blood/,tests/sandboxes/screenshot_circular/). -
CHANGELOG.it.md bumpversion tracking. Italian changelog added to
[tool.bumpversion.files]inpyproject.toml, ensuring version headings stay synchronised across both changelogs duringbump-my-versionruns.
--exit-zerono longer suppresses security exits incheck all. Exit codes 2 (Shield breach) and 3 (Blood Sentinel) were guarded bynot effective_exit_zeroincheck all, contradicting the documented contract ("never suppressed by--exit-zero"). The guards have been removed β exits 2 and 3 are now unconditional, matchingcheck linksandcheck references.
tests/test_cli_e2e.pyβ 8 end-to-end CLI security tests. Full-pipeline tests (no mocks) exercising the exit-code contract:TestBloodSentinelE2E(2 tests) β Blood sandbox triggers Exit 3;--exit-zerodoes NOT suppress it.TestShieldBreachE2E(2 tests) β fake AWS key triggers Exit 2;--exit-zerodoes NOT suppress it.TestExitZeroContractE2E(3 tests) β broken link exits 1;--exit-zerosuppresses to 0; clean sandbox exits 0.TestExitCodePriorityE2E(1 test) β when both security_incident and security_breach coexist, Exit 3 wins. Closes gap:docs/internal/arch_gaps.mdΒ§ "Security Pipeline Coverage".
-
Card Grid Refactoring. Documentation pages standardised to Material for MkDocs grid syntax (
:material-*:icons, consistent column layouts). -
Admonition Normalisation. Ad-hoc callout styles replaced with canonical admonition types (
tip,warning,info,example) per the Sentinel Style Guide. -
Icon Normalisation. Non-Material icons purged; all icons standardised to the
:material-*:icon set. -
102 Strategic Anchor IDs placed across 70 documentation files for stable deep-linking.
-
CSS Card Overrides. Hover effects and consistent card styling added via
docs/assets/stylesheets/.
docs/assets/pdf_cover.html.j2β legacy Jinja2 PDF cover template. Orphan artifact with no build-pipeline reference; removed to reduce maintenance surface.
Alpha 4 Release. Four confirmed vulnerabilities closed (ZRT-001β004), three new hardening pillars added (Blood Sentinel, Graph Integrity, Hex Shield), and full bilingual documentation parity achieved. Pending manual review before Release Candidate promotion.
Branch:
fix/sentinel-hardening-v0.5.0a4
-
Graph Integrity β circular link detection. Zenzic now pre-computes a cycle registry (Phase 1.5) via iterative depth-first search (Ξ(V+E)) over the resolved internal link graph. Any link whose target belongs to a cycle emits a
CIRCULAR_LINKfinding at severityinfo. Mutual navigation links (A β B) are valid documentation structure and are expected; the finding is advisory only β it never affects exit codes in normal or--strictmode. O(1) per-query in Phase 2. Ghost Routes (plugin-generated canonical URLs without physical source files) are correctly excluded from the cycle graph and cannot produce false positives. -
INTERNAL_GLOSSARY.tomlβ bilingual ENβIT term registry (15 entries) for consistent technical vocabulary across English and Italian documentation. Covers core concepts: Safe Harbor, Ghost Route, Virtual Site Map, Two-Pass Engine, Shield, Blood Sentinel, and more. Maintained by S-0. All terms markedstable = truerequire an ADR before renaming. -
Bilingual documentation parity.
docs/checks.mdanddocs/it/checks.mdupdated with Blood Sentinel, Circular Links, and Hex Shield sections.CHANGELOG.it.mdcreated. Full EnglishβItalian parity enforced per the Bilingual Parity Protocol.
-
Blood Sentinel β system-path traversal classification (Exit Code 3).
check linksandcheck allnow classify path-traversal findings by intent. An href that escapesdocs/and resolves to an OS system directory (/etc/,/root/,/var/,/proc/,/sys/,/usr/) is classified asPATH_TRAVERSAL_SUSPICIOUSwith severitysecurity_incidentand triggers Exit Code 3 β a new, dedicated exit code reserved for host-system probes. Exit 3 takes priority over Exit 2 (credential breach) and is never suppressed by--exit-zero. Plain out-of-bounds traversals (e.g.../../sibling-repo/) remainPATH_TRAVERSALat severityerror(Exit Code 1). -
Hex Shield β hex-encoded payload detection. A new built-in Shield pattern
hex-encoded-payloaddetects runs of three or more consecutive\xNNhex escape sequences ((?:\\x[0-9a-fA-F]{2}){3,}). The{3,}threshold avoids false positives on single hex escapes common in regex documentation. Findings exit with code 2 (Shield, non-suppressible) and apply to all content streams including fenced code blocks. -
[ZRT-001] Shield Blind Spot β YAML Frontmatter Bypass (CRITICAL).
_skip_frontmatter()was used as the Shield's line source, silently discarding every line in a file's YAML---block before the regex engine ran. Any key-value pair (aws_key: AKIAβ¦,github_token: ghp_β¦) was invisible to the Shield and would have exitedzenzic check allwith code0. Fix: The Shield stream now uses a rawenumerate(fh, start=1)β every byte of the file is scanned. The content stream (ref-def harvesting) still uses_iter_content_lines()with frontmatter skipping to avoid false-positive link findings from metadata values. This is the Dual-Stream architecture described in the remediation directives. Exploit PoC confirmed via live script: 0 findings before fix, correct detection of AWS / OpenAI / Stripe / GitHub tokens after fix. -
[ZRT-002] ReDoS + ProcessPoolExecutor Deadlock (HIGH). A
[[custom_rules]]pattern like^(a+)+$passed the eager_assert_pickleable()check (pickle is blind to regex complexity) and was distributed to worker processes. TheProcessPoolExecutorhad no timeout: any worker hitting a ReDoS-vulnerable pattern on a long input line hung permanently, blocking the entire CI pipeline. Two defences added: β Canary (prevention):_assert_regex_canary()stress-tests everyCustomRulepattern against three canary strings ("a"*30+"b", etc.) under asignal.SIGALRMwatchdog of 100 ms atAdaptiveRuleEngineconstruction time. ReDoS patterns raisePluginContractErrorbefore the first file is scanned. (Linux/macOS only; silently skipped on Windows.) β Timeout (containment):ProcessPoolExecutor.map()replaced withsubmit()+future.result(timeout=30). A timed-out worker produces aZ009: ANALYSIS_TIMEOUTRuleFindinginstead of hanging the scan. The new_make_timeout_report()and_make_error_report()helpers ensure clean error surfacing in the standard findings UI. Exploit PoC confirmed:^(a+)+$on"a"*30+"b"timed out in 5 s; both defences independently prevent scan lock-up. -
[ZRT-003] Split-Token Shield Bypass β Markdown Table Obfuscation (MEDIUM). The Shield's
scan_line_for_secrets()ran each raw line through the regex patterns once. A secret fragmented across backtick spans and a string concatenation operator (`AKIA` + `1234567890ABCDEF`) inside a Markdown table cell was never reconstructed, so the 20-character contiguousAKIA[0-9A-Z]{16}pattern never matched. Fix: New_normalize_line_for_shield()pre-processor inshield.pyunwraps backtick spans, removes concatenation operators, and collapses table pipes before scanning. Both the raw line and the normalised form are scanned; aseenset prevents duplicate findings when both forms match.
-
[ZRT-004] Context-Aware VSM Resolution β
VSMBrokenLinkRule(MEDIUM)._to_canonical_url()was a@staticmethodwithout access to the source file's directory. Relative hrefs containing..segments (e.g.../../c/target.mdfromdocs/a/b/page.md) were resolved as if they originated from the docs root, producing false negatives: broken relative links in nested files were silently passed. Fix: NewResolutionContextdataclass (docs_root: Path,source_file: Path) added torules.py.BaseRule.check_vsm()andAdaptiveRuleEngine.run_vsm()acceptcontext: ResolutionContext | None(defaultNoneβ fully backwards-compatible)._to_canonical_url()is now an instance method that resolves..segments viaos.path.normpathrelative tocontext.source_file.parentwhen context is provided, then re-maps to a docs-relative posix path before the clean-URL transformation. Paths that escapedocs_rootreturnNone(Shield boundary respected). -
[GA-1] Telemetry / Executor Worker Count Synchronisation.
ProcessPoolExecutor(max_workers=workers)used the rawworkerssentinel (may beNone) while the telemetry reportedactual_workers(always an integer). Both now useactual_workers, eliminating the divergence. -
Stream Multiplexing (
scanner.py).ReferenceScanner.harvest()now explicitly documents its two-stream design: Shield stream (all lines, rawenumerate) and Content stream (_iter_content_lines, frontmatter/fence filtered). Comments updated to make the architectural intent visible to future contributors. -
[Z-SEC-002] Secure Breach Reporting Pipeline (Commit 2). Four structural changes harden the path from secret detection to CI output:
β Breach Panel (
reporter.py): findings withseverity="security_breach"render as a dedicated high-contrast panel (red on white) positioned before all other findings. Surgical caret underlines (^^^^) are positioned using thecol_startandmatch_textfields added toSecurityFinding.β Surgical Secret Masking β
_obfuscate_secret(): raw secret material is never passed to Rich or CI log streams. The function partially redacts credentials (first 4 + last 4 chars; full redaction for strings β€ 8 chars) and is the sole authorised path for rendering secret values in output.β Bridge Function β
_map_shield_to_finding()(scanner.py): a single pure function is the only authorised conversion point between the Shield detection layer andSentinelReporter. Extracted as a standalone function so that mutation testing can target it directly and unambiguously.β Post-Render Exit 2 (
cli.py): the security hard-stop is now applied afterreporter.render(), guaranteeing the full breach panel is visible in CI logs before the process exits with code 2.
-
tests/test_redteam_remediation.pyβ 25 new tests organised in four classes, one per ZRT finding:TestShieldFrontmatterCoverage(4 tests) β verifies Shield catches AWS, GitHub, and multi-pattern secrets inside YAML frontmatter; confirms correct line-number reporting; guards against false positives on clean metadata.TestReDoSCanary(6 tests) β verifies canary rejects classic(a+)+and alternation-based(a|aa)+ReDoS patterns at engine construction; confirms safe patterns pass; verifies non-CustomRulesubclasses are skipped.TestShieldNormalizer(8 tests) β verifies_normalize_line_for_shieldunwraps backtick spans, removes concat operators, collapses table pipes; verifiesscan_line_for_secretscatches split-token AWS key; confirms deduplication prevents double-emit when raw and normalised both match.TestVSMContextAwareResolution(7 tests) β verifies multi-level..resolution from nested dirs, single..from subdirs, absent-from-VSM still emits Z001, path-traversal escape returns no false Z001, backwards compatibility without context,index.mddirectory mapping, andrun_vsmcontext forwarding.
-
tests/test_rules.pyβ_BrokenVsmRule.check_vsm()updated to accept the newcontext=Noneparameter (API compatibility fix). -
731 tests pass. Zero regressions.
pytest --tb=shortβ all green. -
TestShieldReportingIntegrityβ Mutation Gate (Commit 3, Z-TEST-003). Three mandatory tests serving as permanent Mutation Gate guards for the security reporting pipeline:- The Invisible:
_map_shield_to_finding()must always emitseverity="security_breach"β a downgrade to"warning"is caught immediately (assert 'warning' == 'security_breach'). - The Amnesiac:
_obfuscate_secret()must never return the raw secret β removing the redaction logic is caught immediately (assert raw_key not in output). - The Silencer:
_map_shield_to_finding()must never returnNoneβ a bridge function that discards findings is caught immediately (assert result is not None).
Manual verification (The Sentinel's Trial): all three mutants were applied by hand and confirmed killed.
mutmutv3 automatic reporting was blocked by an editable-install interaction (seemutmut_pytest.ini); manual verification accepted per Architecture Lead authorisation (Z-TEST-003). 28 tests intest_redteam_remediation.py, all green. - The Invisible:
- CI/CD deployment pipeline fixed for Node.js 24.
cloudflare/wrangler-action@v3callsnpx wranglerwithout--yes; npm 10+ on Node.js 24 GitHub Actions runners blocks non-interactive prompts, causing the Cloudflare Pages deploy to fail. Fix: pre-installwrangler@latestglobally before the action runs so npx finds the binary in PATH without downloading.FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=truesilences the Node.js 20 deprecation warning ahead of the June 2026 forced migration. Tracked inarch_gaps.md. Branch:fix/v050a4-infra-alignment.
Sprint 13 + 14 + 15. Three tracks delivered in one tag. Track A β Performance & SDK: deterministic two-phase anchor validation,
zenzic.rulespublic namespace, plugin scaffold command, Z001/Z002 split. Track B β Aesthetic & DX: Sentinel Palette with Slate/Indigo/Rose/Amber color identity, unified banner telemetry, agnostic target mode (zenzic check all README.mdorzenzic check all content/),.pre-commit-hooks.yaml, native Material header viaMutationObserver, and two new example projects. Track C β The Breathing Sentinel: nativecol_start/match_textpropagation replacing fragile regex workarounds, surgical caret rendering, traceback gutter with 2-space padding, vertical breathing between findings, and a dedicated success-state panel.
-
src/zenzic/ui.pyβ Sentinel Palette β new module centralising all color and emoji constants for the Sentinel report engine. Ships 8 named constants (INDIGO,SLATE,ROSE,AMBER,STYLE_BRAND,STYLE_DIM,STYLE_ERR,STYLE_WARN), themake_sentinel_header()banner factory, and theemoji()helper with--no-colorguard. -
Agnostic Target Support β
PATHargument oncheck allβzenzic check allaccepts an optional positionalPATHargument scoping the audit to:- Single file β
zenzic check all README.mdaudits exactly one Markdown file; banner reports1 file (1 docs, 0 assets). - Custom directory β
zenzic check all content/patchesdocs_dirat runtime and audits the entire subdirectory; banner reports./content/. VanillaAdapteris selected automatically when the target lies outside the configureddocs_dir(e.g. a root-levelREADME.md)._resolve_target()β resolves absolute/relative paths, checks for.mdextension on files, exits 1 with a clear error on path-not-found._apply_target()β returns(patched_config, single_file_or_None, docs_root, hint); patchesconfig.docs_dirviamodel_copy()(Pydantic v2, no mutation).- Post-hoc filter: after
_to_findings(), results are filtered torel_path == targetin single-file mode so no off-target findings bleed through.
- Single file β
-
Target hint in banner β
make_sentinel_header()acceptstarget: str | None; when set, the hint (e.g../README.md,./content/) appears in the meta line between the engine name and the file count. -
.pre-commit-hooks.yamlβ first-class pre-commit hook definitions shipped in the repository root, enablingrepo: https://github.com/PythonWoods/zenzicin any.pre-commit-config.yaml. -
examples/single-file-target/β new example demonstrating single-file audit (zenzic check all README.md); expected exit 0. -
examples/custom-dir-target/β new example demonstrating directory-target audit (zenzic check all content/); expects exit 0 with./content/ β’ 2 files. -
run_demo.shAct 4 & 5 β Philosophy Tour extended with two new acts validating the single-file and custom-directory target examples automatically. -
zenzic.rulespublic namespace β stable import path for plugin authors:BaseRule,RuleFinding,CustomRule,Violation,Severity(#13). -
run_rule()test helper β single-call rule validation for plugin authors, no engine setup required (#13). -
Z002 orphan-link warning β
VSMBrokenLinkRulenow distinguishes between broken links (Z001 error) and links to orphan pages (Z002 warning) (#6). -
zenzic init --plugin <name>command scaffolds a plugin package with:pyproject.tomlpreconfigured forzenzic.rulesentry-pointssrc/<module>/rules.pymodule-levelBaseRuletemplate- minimal docs fixture and
zenzic.tomlsozenzic check allcan run
-
Smart Initialization β
zenzic init --pyprojectβ whenpyproject.tomlexists,zenzic initinteractively asks whether to embed configuration as a[tool.zenzic]table instead of creating a standalonezenzic.toml. Pass--pyprojectto skip the prompt.--forceoverwrites an existing[tool.zenzic]section. Engine auto-detection works in both modes. -
examples/plugin-scaffold-demo/β living scaffold output fixture for SDK integration checks and contributor onboarding. -
Anchor torture regression test with 1000 cross-linked files to guarantee no race-induced false positives in anchor validation.
-
Native Data Propagation β "The Breathing Sentinel" (
reporter.py,validator.py,scanner.py,rules.py,cli.py) β replaced the fragile_extract_token()regex workaround with nativecol_start/match_textpropagation from every checker through the full pipeline to the reporter. Every regex match site now capturesm.start()andm.group()at detection time and stores them in the finding dataclass β no reverse-engineering from error messages. -
LinkInfoNamedTuple (validator.py) β new type(url, lineno, col_start, match_text)replacing plaintuple[str, int]throughout the validator pipeline.extract_links()andextract_ref_links()now returnlist[LinkInfo]. -
Widened dataclasses β
LinkError,PlaceholderFinding,RuleFinding,Violation, andFindingall gainedcol_start: int = 0andmatch_text: str = ""fields, propagated end-to-end. -
Traceback Gutter (
reporter.py) β increased gutter padding to 2 spaces around the vertical line:16 β± code/16 β code. -
Vertical Breathing (
reporter.py) β emptyText()lines inserted between different findings in the same file, before/after code snippets, after file-separator rules, and before the verdict line. -
Success State (
reporter.py) β when all checks pass, the panel renders a dedicated all-clear layout: telemetry β rule β elegantβ All checks passed. Your documentation is secure.message. -
Surgical Caret Rendering (
reporter.py) β_render_snippet()uses nativecol_start/match_textto render precise^^^^caret underlines; carets are suppressed whencol_start + len(match_text) > 60(wrapping guard) or whenmatch_textis empty (no position data available). -
Removed
_extract_token()(reporter.py) β deleted the_TOKEN_REregex pattern and_extract_token()function; removedimport refrom the module. The reporter no longer guesses token positions from message strings. -
Sentinel Gutter Reporter (
reporter.py) β source-line context block uses the Sentinel Palette consistently:βseparators and line numbers rendered inSLATE- Numeric counts (
N errors,N warnings,N files) rendered inINDIGO - Error row icon and label rendered in
ROSE - Warning row icon and label rendered in
AMBER - All bold removed from report numbers ("evitiamo grassetto" standard).
-
Unified banner counter β
make_sentinel_header()emits a singleN files (D docs, A assets)breakdown replacing the previous separateN docs β’ N assetscounters.mkdocs.ymland other engine config files at project root are included in the docs count. Format:engine β’ [target β’] N files (D docs, A assets) β’ T.Ts. -
has_failureslogic (check_all) β changed fromresults.failed(raw pre-filter counts) to(errors > 0) or (effective_strict and warnings > 0), whereerrorsandwarningsare derived from the post-hoc filteredall_findingslist. Fixes false exit-1 when a target-mode scan filters findings to zero but the full scan found off-target issues (e.g.zenzic.tomllisted as unused asset whendocs_diris patched to.). -
CLI help strings audited β five stale or incorrect help strings corrected:
PATHargument: now documents both file and directory targets with examplescheck all --strict: was "validate external URLs" (wrong); now "treat warnings as errors (exit non-zero on any warning)"check all,score,diffdocstrings updated with target mode notesscore --strict/diff --strict: was vague "Run link check in strict mode"; now "Also validate external HTTP/HTTPS links (slower; requires network)"serve --engine: addedvanillato the engine list
-
Native Material header (
docs/overrides/main.html) βsource.htmlpartial deleted; version is injected into Material's own source facts<ul>via aMutationObserversnippet inmain.html(zero template override, single header row: π· 0.5.0a3 β stars Ο forks). -
Badge rebranding β
cacheBusterparameter in status badge URLs updated fromv050tosentinel-a3inREADME.mdandREADME.it.md; badges rendered as multi-line<a>\n <img>\n</a>for readability. -
"Sentinel in Action" section β three-card visual tour grid added to
docs/index.mdanddocs/it/index.mdillustrating the gutter reporter, Zenzic Shield, and quality score output. -
Script consolidation β
scripts/generate_screenshot.pydeleted; its functionality merged intoscripts/generate_docs_assets.pywhich handles bothscreenshot.svgandscreenshot-score.svg;nox -s screenshotupdated to call the unified script. -
validate_links_asyncnow uses a two-phase model:- Phase 1 (parallel index): workers extract per-file anchors and resolved links.
- Phase 2 (global validation): main process validates links against the merged global anchor index.
-
Architecture docs (
docs/architecture.md,docs/it/architecture.md) Mermaid diagrams now explicitly show worker phases:Phase 1: Anchor Extraction (Parallel)Phase 2: Rule Execution & Validation (Parallel)
-
Plugin SDK docs expanded with "zero to plugin in 30 seconds" fast-track in
docs/developers/plugins.mdanddocs/it/developers/plugins.md. -
Plugin scaffold now imports from
zenzic.rules(public namespace) instead ofzenzic.core.rules(internal) (#13). -
Checks docs updated with Z001/Z002 violation code table in EN/IT (#6).
-
Custom Rules DSL IT docs completed with Performance and Pattern tips (#4).
-
CLI command references updated in EN/IT with
zenzic init --pluginusage. -
docs/usage/advanced.mdβ new "Single-file and directory target" section documentingPATHargument syntax, adapter auto-selection, and use cases (pre-commit hooks, README review, Hugocontent/dirs).
-
mypy errors β
list[object]annotations onsecurity_lineandrenderableschanged tolist[RenderableType](imported fromrich.console);config.docs_dir(typePath) wrapped withstr()when passed toSentinelReporter(docs_dir=...)which expectsstr.mypy src/β 0 errors in 30 files. -
Exit-code under target mode β
has_failuresnow uses filtered findings (errors > 0) rather thanresults.failed(which counted off-target findings such aszenzic.tomlclassified as an unused asset whendocs_dirwas patched to.).test_check_all_no_strict_passes_on_warnings_onlycontinues to pass confirming that warning-only results exit 0 in non-strict mode. -
Unused
# type: ignore[assignment]β stale comment atcli.py:644removed after mypy no longer required it following the_apply_targetrefactor.
test_validator.pyupdated: added_ul()helper to unwrapLinkInfoβ(url, lineno)tuples; 13 assertion patterns updated forLinkInfocompatibility; 4 destructuring patterns migrated fromfor u, _ intofor link inwith.urlattribute access.- 4 new CLI integration tests for target mode:
test_check_all_target_not_found,test_check_all_target_single_file,test_check_all_target_file_outside_docs,test_check_all_target_directory. - Test fixtures write β₯ 60-word bodies to avoid
short-contentplaceholder warnings.
- Mutation score: 86.7% (242/279 mutants killed on
rules.py) β up from 58.1% at baseline. Target was 75%; exceeded by +11.7 pp. - 80 new mutant-killing tests added to
test_rules.py, organised in dedicated test classes:TestExtractInlineLinksWithLines(14 tests) β edge cases for inline link extraction including empty hrefs, escaped brackets, and multi-link lines.TestVSMBrokenLinkRuleMutantKill(22 tests) βcheck_vsmpath/anchor resolution logic, orphan detection, severity mapping, andcontinue/breakbranch coverage.TestAdaptiveRuleEngineRunMutantKill(4 tests) βAdaptiveRuleEngine.run()short-circuit and content propagation.TestAdaptiveRuleEngineRunVsmMutantKill(6 tests) βrun_vsm()VSM-specific finding collection and file iteration.TestAssertPickleableMutantKill(2 tests) βassert_pickleable()deep-copy andUNREACHABLEassertion guard.TestPluginRegistryMutantKill(27 tests) βPluginRegistrydiscovery, duplicate handling, case-sensitivity, andvalidate_rule()contract.TestExtractLinksDeepMutantKill(5 tests) β fence-block skipping, reference link parsing, and empty-document edge cases.
- 37 surviving mutants classified as equivalent (no observable behaviour change) or framework limitations (unreachable defensive assertions).
- Hypothesis property-based testing integrated with three profiles:
dev(50 examples),ci(500),purity(1 000). - mutmut 3.5.0 configured under
[tool.mutmut]inpyproject.toml; runner:python3 -m pytest -x, target:src/zenzic/core/rules.py. - Performance baseline relaxed from 150 ms β 200 ms for 5 000 in-memory resolutions to accommodate CI/nox environmental variance (resolver is O(1)).
- 706 tests pass.
just preflightβ all gates green: ruff β Β· mypy β Β· pytest 80%+ coverage β Β· REUSE β Β· zenzic self-audit β Β· mkdocs build --strict β.
0.5.0a2 β 2026-04-03 β The Refined Sentinel: Lean Package & Unified Workflow
Sprint 12. Consolidation and DX hardening. Removes the
[docs]public extra (engine-agnostic: users install MkDocs independently), eliminatesRELEASE.it.mdin favour of a single English source of truth, and unifies the developer workflow underjust.bump-my-versiongains a PEP 440 pre-release parser/serializer sopre_nbumps work correctly.
[project.optional-dependencies]βzenzic[docs]extra eliminated; MkDocs is a user-managed dependency, not a package extraRELEASE.it.mdβ release notes consolidated toRELEASE.md(English only)- Hardcoded-date
bumpversionpatterns forRELEASE.md,CITATION.cff, anddocs/community/index.md; dates updated manually at release time
justfile:zensical build/servereplaced withmkdocs build --strict/mkdocs serve; addedjust check(self-linting duty);cleannow removesdist/and.zenzic-score.jsonCONTRIBUTING.md: Quick Start updated tojust sync; task table aligned to currentjustcommandsdocs/developers/index.md: added Interactive Workflow with Just section- All
zenzic[docs]references replaced with Lean & Agnostic by Design narrative acrossREADME.md,README.it.md,docs/usage/index.md,docs/it/usage/index.md, and contributor guides pyproject.toml[tool.bumpversion]: addedparse,serialize, andparts.pre_nfor PEP 440 pre-release support; removedmkdocs.ymlentry (version is injected via!ENV, not a literal string)CITATION.cff,docs/community/index.md,docs/it/community/index.md: aligned to0.5.0a2
0.5.0a1 β 2026-04-02 β The Sentinel: Hybrid Adaptive Engine & Plugin System
Sprint 11. Zenzic enters the v0.5 cycle with a unified execution model and a first-class plugin system.
scan_docs_referencesreplaces the two separate serial and parallel functions. The engine selects sequential orProcessPoolExecutormode automatically based on repository size (threshold: 50 files). All rules are validated for pickle-serializability at construction time. Core rules are now registered as entry-points underzenzic.rules, establishing the public plugin contract.
-
scan_docs_referencessignature changed. The function now returnstuple[list[IntegrityReport], list[str]]instead oflist[IntegrityReport]. Callers that ignored link errors must unpack the tuple:# Before (0.4.x) reports = scan_docs_references(repo_root) # After (0.5.x) reports, _ = scan_docs_references(repo_root)
-
scan_docs_references_parallelandscan_docs_references_with_linksare removed. Usescan_docs_references(..., workers=N)andscan_docs_references(..., validate_links=True)respectively. -
RuleEngineis removed. The class is nowAdaptiveRuleEnginewith no alias. The constructor runs eager pickle validation on every rule and raisesPluginContractErrorif any rule is not serialisable.
-
AdaptiveRuleEngine(zenzic.core.rules) β unified rule engine with Hybrid Adaptive Mode. Replaces and removesRuleEngine(no alias). Validates all rules for pickle-serializability at construction time via_assert_pickleable(). -
_assert_pickleable(rule)(zenzic.core.rules) β module-level helper called byAdaptiveRuleEngine.__init__. RaisesPluginContractErroron failure with a diagnostic message including the rule ID, class name, and the pickle error. -
ADAPTIVE_PARALLEL_THRESHOLD(zenzic.core.scanner) β module-level constant (default:50). The file count above which parallel mode activates. Exposed for test overrides without patching private internals. -
PluginContractError(zenzic.core.exceptions) β new exception for rule plugin violations. Added to the exception hierarchy docstring. -
zenzic.rulesentry-point group (pyproject.toml) β core rules registered as first-class plugins:[project.entry-points."zenzic.rules"] broken-links = "zenzic.core.rules:VSMBrokenLinkRule"
-
docs/developers/plugins.md(EN + IT) β new page documenting the rule plugin contract: module-level requirement, pickle safety, purity, packaging viaentry_points,pluginskey inzenzic.toml, error isolation, and a pre-publication checklist. -
docs/developers/index.md(EN + IT) β added link toplugins.md. -
zenzic plugins listβ new CLI sub-command. Lists every rule registered in thezenzic.rulesentry-point group with itsrule_id, origin distribution, and fully-qualified class name. Core rules are labelled(core); third-party rules show the installing package name. -
pyproject.tomlconfiguration support (ISSUE #5) βZenzicConfig.load()now follows a three-level Agnostic Citizen priority chain:zenzic.toml(Priority 1) β[tool.zenzic]inpyproject.toml(Priority 2) β built-in defaults (Priority 3). If both files exist,zenzic.tomlwins unconditionally. -
pluginsconfig key (zenzic.toml/[tool.zenzic]) βZenzicConfig.pluginsnow exposes an explicit allow-list of external rule plugin entry-point names to activate during scanning. Core rules remain always enabled. -
scan_docs_referencesverboseflag β new keyword-only parameterverbose: bool = False. WhenTrue, prints a one-line performance telemetry summary to stderr after the scan: engine mode (Sequential or Parallel), worker count, file count, elapsed time, and estimated speedup (parallel mode only). -
PluginRuleInfodataclass (zenzic.core.rules) β lightweight struct returned by the newlist_plugin_rules()discovery function. Fields:rule_id,class_name,source,origin. -
docs/configuration/index.md(EN + IT) β "Configuration loading" section expanded with the three-level priority table and a[tool.zenzic]example.
-
scan_docs_references(zenzic.core.scanner) β unified function replacingscan_docs_references+scan_docs_references_parallel. New signature:scan_docs_references( repo_root, config=None, *, validate_links=False, workers=1 ) -> tuple[list[IntegrityReport], list[str]]
Hybrid Adaptive Mode: sequential when
workers=1or< 50 files; parallel (ProcessPoolExecutor) otherwise. Results always sorted byfile_path. -
docs/architecture.mdanddocs/it/architecture.mdβ "Parallel scan (v0.4.0-rc5)" section replaced by "Hybrid Adaptive Engine (v0.5.0a1)" with a Fan-out/Fan-in Mermaid diagram showing the threshold decision node. IT section was previously absent; added from scratch. -
docs/usage/advanced.mdanddocs/it/usage/advanced.mdβ parallel scan section rewritten to document the unifiedscan_docs_referencesAPI and the Hybrid Adaptive Engine threshold table. -
docs/usage/commands.md(EN + IT) β addedzenzic plugins listcommand documentation and--workersflag reference for the Hybrid Adaptive Engine. -
README.mdβ "RC5 Highlights" replaced by "v0.5.0a1 Highlights β The Sentinel". -
pyproject.tomlβ version bumped to0.5.0a1. -
src/zenzic/__init__.pyβ__version__bumped to"0.5.0a1".
scan_docs_references_parallelβ deleted; usescan_docs_references(..., workers=N).scan_docs_references_with_linksβ deleted; usescan_docs_references(..., validate_links=True).RuleEngineβ deleted; useAdaptiveRuleEnginedirectly.
This release cycle was exploratory and included multiple breaking changes. It has been superseded by the 0.5.x stabilization cycle.
0.4.0-rc4 β 2026-04-01 β Ghost Route Support, VSM Rule Engine & Content-Addressable Cache
0.4.0-rc5 β 2026-04-01 β The Sync Sprint: Zensical v0.0.31+ & Parallel API
Sprint 10.
ZensicalAdapteris fully synchronised with Zensical v0.0.31+. The legacy[site]/[nav].navschema is replaced by the canonical[project].navformat. Navigation parsing now supports all three entry forms (plain string, titled page, nested section) recursively.classify_route()gains nav-aware orphan detection.map_url()honoursuse_directory_urls = false. The parallel scan API is stabilised and documented with explicit pickling requirements for custom rules.examples/zensical-basic/is introduced as the canonical Zensical reference project. All Zensical TOML snippets indocs/are updated to the v0.0.31+ schema.
-
examples/zensical-basic/β new canonical example project forengine = "zensical". Contains azensical.tomlusing[project].navwith all three nav entry forms, azenzic.tomlwithengine = "zensical", and a completedocs/tree with clean relative links.zenzic check allon this example exits 0. -
_extract_nav_paths()(zenzic.core.adapters._zensical) β new pure helper that recursively extracts.mdfile paths from a Zensical nav list. Handles plain strings ("page.md"), titled pages ({"Title" = "page.md"}), and nested sections ({"Section" = [...]}). External URLs are silently skipped. -
Nav-aware
classify_route()β when an explicit[project].navis declared inzensical.toml, files absent from the nav list are now classifiedORPHAN_BUT_EXISTINGinstead ofREACHABLE. Zensical serves every file (filesystem routing), but the sidebar is the user-visible navigation: files outside the nav are effectively invisible. -
use_directory_urlssupport inZensicalAdapter.map_url()β reads[project].use_directory_urlsfromzensical.toml. Whenfalse, files are mapped to flat.htmlURLs (/page.html) instead of directory URLs (/page/). Default remainstrue, matching Zensical's own default. -
Parallelism section in
docs/usage/advanced.mdβ documentsscan_docs_references_parallel, the performance crossover point (~200 files), the absence of a--parallelCLI flag in rc5, and the pickling requirements for customBaseRulesubclasses. -
Parallelism section in
docs/architecture.mdβ describes the shared-nothingProcessPoolExecutormodel, the immutability contract on workers, and the performance threshold with honest numbers. -
Engine coexistence section in
docs/configuration/adapters-config.md(EN + IT) β documents behaviour when bothmkdocs.ymlandzensical.tomlare present. Clarifies thatbuild_context.engineis always authoritative; no auto-detection occurs. -
ZensicalAdapternav format reference indocs/configuration/adapters-config.md(EN + IT) β full TOML examples of all three nav entry forms, route classification rules with and without explicit nav, anduse_directory_urlsdocumentation.
-
ZensicalAdapter.__init__β pre-computes_nav_paths,_has_explicit_nav, and_use_directory_urlsat construction time from[project]inzensical.toml.get_nav_paths()is now an O(1) attribute read. -
ZensicalAdapterdocstring β updated from legacy[nav].nav = [{title, file}]schema to the v0.0.31+[project].nav = [...]format. -
tests/sandboxes/zensical/zensical.tomlβ migrated from flat key schema to[project]scope. Added a three-entrynavlist (index.md,features.md,api.md) to exercise orphan detection in integration tests. -
docs/guide/migration.mdanddocs/it/guide/migration.mdβ Phase 3 examplezensical.tomlupdated from legacy[site]/[nav].navto[project].navformat.
-
ZensicalAdapter.get_nav_paths()previously read[nav].navusing{title, file}key format β a schema that was never part of the official Zensical spec. Fixed to read[project].navusing the actual v0.0.31+ format. -
ZensicalAdapter.classify_route()previously returnedREACHABLEfor all non-private files regardless of nav declaration. Fixed to returnORPHAN_BUT_EXISTINGwhen an explicit nav is declared and the file is absent from it.
0.4.0-rc4 β 2026-04-01 β Ghost Route Support, VSM Rule Engine & Content-Addressable Cache
Sprint 9. The Virtual Site Map (VSM) becomes the single source of truth for the Rule Engine. MkDocs Material Ghost Routes are resolved without false orphan warnings. The
VSMBrokenLinkRulevalidates links against routing state rather than the filesystem. A content-addressable cache eliminates redundant re-linting of unchanged files. TheViolationdataclass introduces a structured finding standard withcode,level, andcontextfields.RuleFindingis scheduled for removal before final release.
-
Ghost Route support (
MkDocsAdapter) β whenmkdocs.ymldeclaresplugins.i18n.reconfigure_material: true, the Material theme auto-generates locale entry points (e.g.it/index.mdβ/it/) at build time. These pages are never listed innav:but are live routes.classify_route()now marks top-level locale index files asREACHABLEwhen this flag is set, eliminating falseORPHAN_BUT_EXISTINGfindings. Guarded todocs_structure: folderonly; suffix mode is unaffected. -
Redundant config warning β
MkDocsAdapter.__init__emits alogging.WARNINGwhen bothreconfigure_material: trueandextra.alternateare present inmkdocs.yml. The combination suppresses the language switcher in some plugin versions. The warning names the exact key to remove. Detection via the new pure helper_detect_redundant_alternate(). -
Violationdataclass (zenzic.core.rules) β structured finding type for VSM-aware rules. Fields:code(e.g.Z001),level(error/warning/info),context(raw source line).as_finding()converts toRuleFindingfor backwards compatibility during the transition period. -
BaseRule.check_vsm()β new optional method onBaseRule. Receives(file_path, text, vsm, anchors_cache)β all in-memory, no I/O. Default no-op preserves full backwards compatibility for existingcheck()-only subclasses. -
RuleEngine.run_vsm()β companion torun(). Callscheck_vsmon every rule, convertsViolationobjects toRuleFinding, and isolates exceptions identically torun(). -
VSMBrokenLinkRule(codeZ001) β first VSM-aware built-in rule. Validates every inline Markdown link againstvsm[url].status. A link is valid only when its target URL isREACHABLEin the VSM β meaning Ghost Routes, nav-listed pages, and locale shadows all pass cleanly. Orphan and ignored pages emitUNREACHABLE_LINK. External URLs and bare fragments are skipped.check()is a documented no-op. -
CacheManager(zenzic.core.cache) β content-addressable in-memory cache for rule findings. Key:SHA256(content) + SHA256(config) [+ SHA256(vsm_snapshot)].- Atomic rules (e.g.
CustomRule): key omits VSM hash; cache survives unrelated file changes. - Global rules (e.g.
VSMBrokenLinkRule): key includes VSM hash; entire routing-state change invalidates entries. - Persistence:
load()/save()are the only I/O operations;get()/put()are pure in-memory. Atomic write via temp-file rename prevents corruption. - Degrades silently to cold start on missing or corrupt cache file.
- Atomic rules (e.g.
-
make_vsm_snapshot_hash()β pure function that produces a stable SHA-256 digest of VSM routing state (url, source, status, anchors). Deterministic: routes sorted by URL, anchor sets sorted before serialisation. -
Rule dependency taxonomy β documented in
rules.pymodule docstring: Atomic (single-file, cache key omits VSM), Global (VSM-aware, cache key includes VSM snapshot), Cross-file (future; not cacheable per-file without a dependency graph).
-
classify_route()docstring β enumeration extended to rule 3 (Ghost Route) and rule 4 (ORPHAN fallback) with explicit note that rules fire in priority order and an explicit nav entry always wins. -
README
## First-Class Integrationsβ new "How it works β Virtual Site Map (VSM)" subsection explains the VSM pipeline, Ghost Routes, and content-addressable cache.
extra.alternate+reconfigure_materialconflict (mkdocs.yml) β removed the redundantextra.alternateblock from the project's ownmkdocs.yml. This was causing the language switcher to disappear inmkdocs-materialwhenreconfigure_material: trueis active.
docs/guide/migration.mdanddocs/it/guide/migration.mdβ new "MkDocs Material best practices / Language switcher optimisation" section explains thereconfigure_materialvsextra.alternateconflict and provides the recommended configuration pattern.
TestGhostRouteReconfigureMaterial(9 tests) β Ghost Route invariants: positive path (reconfigure_material: trueβREACHABLE), regression path (flag off/absent βORPHAN_BUT_EXISTINGfor non-shadow pages), boundary (explicit nav entry not overridden, nested locale pages not promoted), end-to-end VSM integration.TestViolationβViolationcontract: fields,is_error,as_finding()round-trip.TestVSMBrokenLinkRuleβ 11 tests covering REACHABLE pass, missing URL, orphan status, external skip, fragment skip, fenced-block skip, context/line-number correctness,run_vsmengine integration.TestRuleEngineTortureTestβ O(N) scalability: 10 000-node VSM + 10 000 links completes in < 1 s for both all-valid and all-missing cases.
0.4.0-rc3 β 2026-03-31 β Virtual Site Map, UNREACHABLE_LINK & Routing Collision Detection
Sprint 8. Zenzic gains build-engine emulation: the Virtual Site Map (VSM) projects every source file to its canonical URL before the build runs. Links to pages that exist on disk but are absent from the nav are now caught as
UNREACHABLE_LINK. Routing collisions (e.g.index.md+README.mdcoexisting in the same directory) are flagged asCONFLICT. Documentation paths unified under/guide/. Terminology aligned to "compatible successor".
-
Virtual Site Map (VSM) β new
zenzic.models.vsmmodule introduces theRoutedataclass (url,source,status,anchors,aliases) andbuild_vsm(), a zero-I/O function that projects every.mdsource file to its canonical URL and routing status (REACHABLE,ORPHAN_BUT_EXISTING,IGNORED,CONFLICT) using the active build-engine adapter. Zenzic now emulates the site router without running the build. -
UNREACHABLE_LINKdetection βvalidate_links_asyncnow cross-references every successfully resolved internal link against the VSM. A link to a file that exists on disk but is not listed in the MkDocsnav:emitsUNREACHABLE_LINK, catching the class of 404s that traditional file-existence checks miss entirely. Disabled automatically forVanillaAdapterandZensicalAdapter(filesystem-only routing). -
Routing collision detection β
_detect_collisions()invsm.pymarks any two source files that map to the same canonical URL asCONFLICT. The most common case βindex.mdandREADME.mdcoexisting in the same directory (Double Index) β is handled without special-casing: both produce the same URL and are therefore caught automatically. -
map_url()andclassify_route()adapter methods β added toBaseAdapterProtocol,MkDocsAdapter,ZensicalAdapter, andVanillaAdapter.map_url(rel)applies engine-specific physical β virtual URL mapping;classify_route(rel, nav_paths)returns the routing status for a given source file.
-
MkDocsAdapter.classify_routeβ when nonav:section is declared inmkdocs.yml, all files are classified asREACHABLE(mirrors MkDocs auto-include behaviour).README.mdremainsIGNOREDregardless. -
Documentation paths β all references to the stale
/guides/path inRELEASE.md,RELEASE.it.md,CHANGELOG.md,CHANGELOG.it.md, andREADME.it.mdupdated to the canonical/guide/root.
- Terminology β "Zensical is a superset of MkDocs" replaced with "Zensical is a compatible successor to MkDocs" across all documentation and changelog entries.
0.4.0-rc3 β 2026-03-29 β i18n Anchor Fix, Multi-language Snippets & Shield Deep-Scan
Sprint 7. The
AnchorMissingi18n fallback gap closed. Dead code eliminated. Shared locale path-remapping utility extracted. Visual Snippets for custom rule findings. Usage docs split into three focused pages. JSON schema stabilised at 7 keys. Multi-language snippet validation (Python/YAML/JSON/TOML) and full-file Shield deep-scan added.
-
Multi-language snippet validation β
check_snippet_contentnow validates fenced code blocks for four languages using pure Python parsers (no subprocesses):python/pyβcompile();yaml/ymlβyaml.safe_load();jsonβjson.loads();tomlβtomllib.loads(). Blocks with unsupported language tags (e.g.bash) are silently skipped._extract_python_blocksrenamed to_extract_code_blocksto reflect the broader scope. -
Shield deep-scan β credentials in fenced blocks β The credential scanner now operates on every line of the source file, including lines inside fenced code blocks (labelled or unlabelled). Previously,
_iter_content_linesfed both the Shield and the reference harvester, causing fenced content to be invisible to the Shield. A new_skip_frontmattergenerator provides a raw line stream (minus frontmatter only);harvest()now runs two independent passes β Shield on the raw stream, ref-defs + alt-text on the filtered content stream. Links and reference definitions inside fenced blocks remain ignored to prevent false positives. -
Shield extended to 7 credential families β Added Stripe live keys (
sk_live_[0-9a-zA-Z]{24}), Slack tokens (xox[baprs]-[0-9a-zA-Z]{10,48}), Google API keys (AIza[0-9A-Za-z\-_]{35}), and generic PEM private keys (-----BEGIN [A-Z ]+ PRIVATE KEY-----) to_SECRETSincore/shield.py. -
resolve_anchor()method onBaseAdapterprotocol β New adapter method that returnsTruewhen an anchor miss on a locale file should be suppressed because the anchor exists in the default-locale equivalent. Implemented inMkDocsAdapter,ZensicalAdapter(viaremap_to_default_locale()), andVanillaAdapter(always returnsFalse). -
adapters/_utils.pyβremap_to_default_locale()pure utility β Extracts the shared locale path-remapping logic that was independently duplicated acrossresolve_asset()andis_shadow_of_nav_page()in both adapters. Pure function: takes(abs_path, docs_root, locale_dirs), returns the default-locale equivalentPathorNone. Zero I/O. -
Visual Snippets for
[[custom_rules]]findings β Custom rule violations now display the offending source line below the finding header, prefixed with theβindicator rendered in the finding's severity colour. Standard check findings are unaffected. -
strictandexit_zeroaszenzic.tomlfields β Both flags are now first-classZenzicConfigfields (typebool | None, sentinelNone= not set). CLI flags override TOML values. Enables project-level defaults without CLI ceremony. -
JSON output schema β 7 stable keys β
--format jsonemits:links,orphans,snippets,placeholders,unused_assets,references,nav_contract. -
Usage docs split β
docs/usage/index.mdsplit into three focused pages:usage/index.md(install + workflow),usage/commands.md(CLI reference),usage/advanced.md(three-pass pipeline, Shield, programmatic API, multi-language). Italian mirrors (docs/it/usage/) at full parity.mkdocs.ymlnav updated.
AnchorMissinghad no i18n fallback suppression β TheAnchorMissingbranch invalidate_links_asyncreported unconditionally. Links to translated headings in locale files generated false positives. Fix:AnchorMissingbranch now callsadapter.resolve_anchor(). Five new integration tests inTestI18nFallbackIntegrationcover: suppressed miss, miss in both locales, fallback disabled, EN source file, direct resolution.
_should_suppress_via_i18n_fallback()β Dead code. Was defined invalidator.pybut never called. Removed permanently.I18nFallbackConfigNamedTuple β Internal data structure for the above deleted function. Removed._I18N_FALLBACK_DISABLEDsentinel β Constant for the above deleted function. Removed._extract_i18n_fallback_config()β Also dead. Was tested byTestI18nFallbackConfig(6 tests), which is also removed. Total removal: ~118 lines fromvalidator.py.
- 5 new anchor fallback integration tests in
TestI18nFallbackIntegration. TestI18nFallbackConfig(6 tests for deleted functions) removed.- 8 new snippet validation tests (YAML valid/invalid,
ymlalias, JSON valid/invalid, JSON line-number accuracy, TOML valid/invalid). - 5 new Shield deep-scan tests: secret in unlabelled fence, secret in
bashfence, secret in fence with no ref-def created, clean code block no findings, combined invariant. - 446 tests pass.
nox preflightβ all gates green: ruff β mypy β pytest β reuse β mkdocs build --strict β zenzic check all --strict β.
0.4.0-rc2 β 2026-03-28 β The Great Decoupling
Sprint 6. Zenzic ceases to own its adapters. Third-party adapters install as Python packages and are discovered at runtime via entry-points. The Core never imports a concrete adapter again. Also promotes the documentation from a collection of pages to a structured knowledge base with i18n parity.
-
zenzic clean assetscommand β New interactive autofix command that automatically deletes unused images and assets from the repository. By default, it prompts[y/N]for safety. Supports-y/--yesfor CI/CD automation. Can be run safely afterzenzic check assets. -
Dynamic Adapter Discovery (
_factory.py) βget_adapter()no longer importsMkDocsAdapterorZensicalAdapterdirectly. The factory queriesimportlib.metadata.entry_points(group="zenzic.adapters")at runtime. Installing a package that registers itself under this group makes it immediately available as--engine <name>; no Zenzic release required. Built-in adapters (mkdocs,zensical,vanilla) are registered inpyproject.toml:[project.entry-points."zenzic.adapters"] mkdocs = "zenzic.core.adapters:MkDocsAdapter" zensical = "zenzic.core.adapters:ZensicalAdapter" vanilla = "zenzic.core.adapters:VanillaAdapter"
-
from_repo()classmethod pattern β Adapters own their own config loading and enforcement contract. The factory callsAdapterClass.from_repo(context, docs_root, repo_root)when present, falling back to__init__(context, docs_root)for backwards-compatible adapters. -
has_engine_config()protocol method (BaseAdapter) β Replaces the previousisinstance(adapter, VanillaAdapter)check inscanner.py. The scanner is now fully decoupled from all concrete adapter types; it only importsget_adapterand speaks theBaseAdapterprotocol. -
list_adapter_engines() -> list[str]β Public function returning the sorted list of registered adapter engine names. Used by the CLI--enginevalidation path. -
--engine ENGINEflag oncheck orphansandcheck allβ Overridesbuild_context.enginefor a single run without modifyingzenzic.toml. Validated dynamically against installed adapters; unknown names produce a friendly error listing available choices:ERROR: Unknown engine adapter 'hugo'. Installed adapters: mkdocs, vanilla, zensical Install a third-party adapter or choose from the list above. -
[[custom_rules]]DSL β Project-specific lint rules declared inzenzic.tomlas a pure-TOML array-of-tables. Each rule applies a compiled regular expression line-by-line to every.mdfile. Rules are adapter-independent: they fire identically withmkdocs,zensical, andvanillaadapters. Patterns are compiled once at config-load time. Invalid regex patterns raiseConfigurationErrorat startup. Fields:id(required),pattern(required),message(required),severity("error"|"warning"|"info", default"error"). See Custom Rules DSL. -
ZensicalAdapter.from_repo()enforcement contract β Whenengine = "zensical"is declared,zensical.tomlmust exist at the repository root.from_repo()raisesConfigurationErrorimmediately if it is absent. No silent fallback tomkdocs.yml. -
MkDocsAdapter.config_file_foundtracking βfrom_repo()records whethermkdocs.ymlwas actually found on disk (independently of whether it parsed successfully).has_engine_config()returnsTruewhen the file existed, allowing orphan detection to run even when the YAML is malformed. -
zenzic initcommand β Scaffolds azenzic.tomlat the repository root with smart engine discovery. Detectsmkdocs.ymlβ pre-setsengine = "mkdocs"; detectszensical.tomlβ pre-setsengine = "zensical"; no engine config found β Vanilla scaffold. All settings are commented out by default.--forceoverwrites an existing file. -
UX Helpful Hint panel β When any
checkcommand runs without azenzic.toml, Zenzic prints an informational Rich panel guiding the user tozenzic init. The hint is suppressed automatically oncezenzic.tomlexists. Driven by the newloaded_from_file: boolflag returned byZenzicConfig.load(). -
ZenzicConfig.load()returnstuple[ZenzicConfig, bool]β The second element (loaded_from_file) isTruewhenzenzic.tomlwas found and parsed,Falsewhen built-in defaults are in use. Callers incli.py,plugin.py,scanner.py, andvalidator.pyupdated accordingly. -
Documentation β Configuration split β
configuration.mdde-densified into a three-page reference underdocs/configuration/: Overview Β· Core Settings Β· Adapters & Engine Β· Custom Rules DSL -
Documentation β Italian parity β
docs/it/now mirrors the full English structure:it/configuration/(4 pages),it/developers/writing-an-adapter.md,it/guide/migration.md. -
Documentation β Writing an Adapter guide (
docs/developers/writing-an-adapter.md) β Full protocol reference:BaseAdaptermethods,from_repopattern, entry-point registration, test utilities (RepoBuilder,assert_no_findings, protocol compliance checker). -
Documentation β MkDocs β Zensical migration guide (
docs/guide/migration.md) β Four-phase migration workflow: establish baseline β switch binary β declare identity β verify link integrity. Includes[[custom_rules]]portability note and quick-reference table.
-
Unknown engine β
VanillaAdapter(breaking from v0.3 behaviour) β Previously, an unknownenginestring caused a fallback toMkDocsAdapter. Now it falls back toVanillaAdapter(no-op orphan check), consistent with "no registered adapter = no engine-specific knowledge". -
scanner.pyis now protocol-only β removedVanillaAdapterimport; replacedisinstance(adapter, VanillaAdapter)withnot adapter.has_engine_config(). The scanner has zero dependency on any concrete adapter class. -
output_formatparameter (wasformat) β Renamed incheck_all,score, anddiffCLI commands to avoid shadowing the Python built-informat. Affects programmatic callers using the internal_collect_all_resultsAPI; the--formatCLI flag is unchanged.
-
check allnow runs 7/7 checks β The reference integrity pipeline (scan_docs_references_with_links) was never invoked bycheck all. Dangling references and Shield events could pass the global gate silently. Fixed:_collect_all_resultsnow calls the reference pipeline._AllCheckResultsgainsreference_errorsandsecurity_eventsfields. Shield exit code2is enforced unconditionally. JSON output gains a"references"key. -
Stale
docs/it/configuration.mdghost file β The Italian configuration god-page was never deleted after the configuration split intodocs/it/configuration/. The orphan checker correctly skips locale subtrees by design; the file was a physical ghost. Fix: file deleted. -
rule_findingssilently discarded incheck referencesβIntegrityReport.rule_findingswas populated by the scanner but never iterated in thecheck referencesCLI output loop. Custom rule violations were invisible to users. Fixed by adding iteration overreport.rule_findingsin the output path.
-
find_repo_root()root marker β changed frommkdocs.ymlto.gitorzenzic.toml. Zenzic no longer requires an MkDocs configuration file to locate the repository root. Engine-agnostic by design. -
O(N) reads β
scan_docs_references_with_linkspreviously read each file twice (Phase A harvest + Phase B link registration). New_scan_single_file()helper returns(IntegrityReport, ReferenceScanner | None)and reuses the scanner object for URL registration, eliminating the double-read bottleneck. -
[[custom_rules]]regex pre-compilation βplaceholder_patterns_compiledfield added toZenzicConfigviamodel_post_init. Patterns are compiled once at config-load time viamodel_post_init, not per-file during scanning. -
YAML frontmatter skipping β
_iter_content_lines()now skips the leading---YAML frontmatter block. Previously, frontmatter was passed to the Shield and placeholder scanner, causing falseSECRETandplaceholder-textfindings on pages with valid frontmatter values that happened to match credential or stub patterns. -
Image reference false positives β
_RE_REF_LINKregex now includes a(?<!!)negative lookbehind. Image references (![alt][id]) no longer generate falseDANGLING_REFERENCEfindings; group indices updated to(1=full, 2=text, 3=ref_id). -
Shield prose scanning β
scan_line_for_secretsintegrated into theharvest()loop on non-definition lines (defence-in-depth). Credentials embedded in prose β not just in reference URLs β are now detected during Pass 1. No duplicateSECRETevents. -
Percent-encoded asset filenames β
check_asset_references()appliesunquote()to decoded URL paths beforenormpath. Filenames likelogo%20v2.pngare now correctly matched against the filesystem and do not generate false "unused asset" findings. -
subprocessimport isolation β moved insideserve()body; no longer pollutes the CLI module namespace at import time.
- Shield-as-firewall in prose (CVE-2026-4539 hardening) β The credential scanner now
runs on every non-definition line during Pass 1, not only on reference URL values. A
credential copied into a Markdown paragraph rather than a reference link is caught before
any URL validation or HTTP request is issued. Exit code
2remains reserved exclusively for Shield events and cannot be suppressed by any flag.
test_vanilla_mode.py::test_get_adapter_unknown_engine_falls_back_to_vanillaupdated β unknown engine now correctly returnsVanillaAdapter(wasMkDocsAdapter).test_rules.pyβ added parametrized cross-adapter test verifying[[custom_rules]]fire identically formkdocs,zensical, andautoengines.- 435 tests pass.
zenzic check allβ 7/7 OK on the project's own documentation (self-dogfood, full i18n parity verified).
Sprint 5. Eleven production-grade fixes applied to
0.4.0-alpha.1. Engine-agnostic root discovery, O(N) read budget, frontmatter skip, Shield prose scanning, image-ref false-positive elimination.
validate_same_page_anchorsβ newzenzic.tomlboolean field (defaultfalse). When enabled, same-page anchor links ([text](#section)) are validated against the ATX headings extracted from the source file. Disabled by default because anchor IDs can also originate from HTML attributes, plugins, or build-time macros invisible at source-scan time.excluded_external_urlsβ newzenzic.tomllist field (default[]). URL prefixes listed here are skipped by the external broken-link checker. Prefix-based matching: a single entry covers an entire domain or repository subtree.- Community section β new documentation section: Get Involved, FAQs, Contributing guide, Report a Bug, Docs Issue, Request a Change, Pull Requests.
excluded_build_artifactsandexcluded_asset_dirsβ two newzenzic.tomlfields covering build-time generated files and theme override directories.
find_repo_root()root marker βmkdocs.ymlreplaced by.git+zenzic.toml. Zenzic no longer requires an MkDocs configuration to locate the repository root.check_allrefactored β_AllCheckResultsdataclass +_collect_all_results()replace duplicated JSON/text code paths; all 6 checks (including References) are now covered uniformly.formatβoutput_formatparameter incheck_all,score,diffβ eliminates ruff A002 built-in-shadowing warning.- Expanded
placeholder_patternsdefaults β 23 EN/IT stub conventions now built in.
slug_heading()MkDocs Material explicit anchors β{ #custom-id }attribute syntax now correctly resolved;{ #id }tokens no longer treated as heading text.- HTML tags stripped before slugification β inline HTML in heading text removed before slug computation.
check_referencesoutput β file paths relativized todocs_rootfor CI/CD-friendly display.
- 405 tests pass.
zenzic check allβ 6/6 OK.
Breaking release candidate. Introduces the Adapter Pipeline β a clean architectural boundary between the Core validator and any build engine. Migrates project documentation to i18n Folder Mode (
docs/it/). Closes the RC3 cycle and opens the 0.4.0 Alpha.
- i18n Folder Mode migration β Project documentation moved from Suffix Mode
(
docs/page.it.md) to Folder Mode (docs/it/page.md), matching themkdocs-static-i18ndocs_structure: folderconvention. URL structure changes from/page.it/to/it/page/. Projects using Zenzic's own docs as a reference must update any hardcoded locale-suffixed paths. [build_context]table must be declared last inzenzic.tomlβ TOML table headers apply to all subsequent keys; placing[build_context]before other top-level fields silently swallowed them. Correct ordering: all top-level fields first,[build_context]at the end.
BuildContextmodel β new[build_context]section inzenzic.tomldeclaringengine,default_locale,locales, andfallback_to_default. Provides Zenzic's Core with locale topology without parsingmkdocs.ymlat validation time.MkDocsAdapter(zenzic.core.adapter) β build-engine adapter implementing three engine-agnostic methods:is_locale_dir(),resolve_asset(),is_shadow_of_nav_page(). Handles bothmkdocsandzensicalengines (identical folder-mode conventions).get_adapter()factory β single entry point; returns the appropriate adapter for the declared engine. Extension point for futureZensicalAdapterorHugoAdapter.- Automatic
mkdocs.ymlfallback β whenbuild_context.localesis empty (nozenzic.toml), bothvalidator.pyandscanner.pyread locale dirs andfallback_to_defaultfrommkdocs.yml. Zero-configuration projects are unaffected. - Nav restructure β five semantic sections: Home / Getting Started / User Guide /
Technical Reference / Community. Italian
nav_translationsfor all 18 keys. extra.alternateblock restored inmkdocs.ymlβ required for Zensical's Jinja2 template to render the language selector;reconfigure_material: truealone is insufficient when the i18n plugin runs as Python (not Rust).
- False-positive orphans β all 14
docs/it/**/*.mdfiles were reported as orphans because the old logic used a blanket locale-dir skip derived frommkdocs.yml. The new adapter checksis_locale_dir()viaBuildContext, which is populated either fromzenzic.tomlor from themkdocs.ymlfallback. Zero orphan false positives. - False-positive broken links β asset links in
docs/it/index.md(e.g.assets/brand/svg/zenzic-wordmark.svg) resolved todocs/it/assets/β¦(non-existent).MkDocsAdapter.resolve_asset()now strips the locale prefix and checks the default-locale tree, mirroring the build engine's actual fallback behaviour. header.htmlSPDX tag β Jinja2 whitespace-stripping tags ({#- β¦ -#}) causedreuseto parseApache-2.0 -as an invalid SPDX expression. Replaced with{# β¦ #}.overrides/location β moved fromdocs/overrides/to project rootoverrides/so that override files are not scanned as documentation pages.
validator.pyβ replaced_extract_i18n_fallback_config/_should_suppress_via_i18n_fallback(YAML-parsing procedural logic) withadapter.resolve_asset().ConfigurationErroris still raised via_extract_i18n_fallback_configfor the validation-only path.scanner.pyβ replaced_extract_i18n_locale_dirs+ blanket skip withadapter.is_locale_dir(). Added_extract_i18n_fallback_to_default()helper.
- All 384 tests pass.
zenzic check allβ 6/6 OK on the project's own documentation.
Note: Builds on
0.3.0-rc2. Adds the Trinity of Examples (Gold Standard, Broken Docs, Security Lab), ISO 639-1 enforcement in suffix detection, and 20 chaos tests. This is the final Release Candidate before the0.3.0stable tag.
- Trinity of Examples β three reference directories in
examples/that cover the full spectrum of documentation integrity:examples/i18n-standard/β the Gold Standard: deep hierarchy, suffix mode, ghost artifacts (excluded_build_artifacts), zero absolute links, 100/100.examples/broken-docs/β updated with absolute link violation and broken i18n link to demonstrate the Portability Enforcement Layer and cross-locale validation.examples/security_lab/β updated withtraversal.mdandabsolute.md; four distinct Shield and Portability triggers, all verified.
examples/run_demo.shPhilosophy Tour β three-act orchestrator: Act 1 Standard (must pass), Act 2 Broken (must fail), Act 3 Shield (must block).- Ghost Artifact Demo β
examples/i18n-standard/referencesassets/manual.pdfandassets/brand-kit.zipviaexcluded_build_artifacts. Zenzic scores green without the files on disk β live proof of Build-Aware Intelligence.
- ISO 639-1 guard β
_extract_i18n_locale_patternsnow validates locale strings withre.fullmatch(r'[a-z]{2}', locale). Version tags (v1,v2), build tags (beta,rc1), numeric strings, BCP 47 region codes, and uppercase values are silently rejected. Only two-letter lowercase codes produce*.locale.mdexclusion patterns.
tests/test_chaos_i18n.pyβ 20 chaos scenarios (ISO 639-1 guard Γ 11, orphan check pathological Γ 9). 367 passed, 0 failed.
Note: Builds on
0.3.0-rc1. Adds the Portability Enforcement Layer (absolute link prohibition) and migrates project documentation to engine-agnostic Suffix Mode i18n. Zenzic's i18n validation now works with any documentation engine without plugin dependency.
- Absolute Link Prohibition β Links starting with
/now trigger a blocking error. Absolute paths are environment-dependent: they break when documentation is hosted in a subdirectory (e.g.site.io/docs/). Zenzic enforces relative paths (../or./) to make documentation portable across any hosting environment. Error message includes an explicit fix suggestion. - Agnostic Suffix-based i18n β Support for the non-nested translation pattern
(
page.locale.md). Zenzic detects locale suffixes from file names independently of any build-engine plugin. This makes i18n validation work with Zensical, MkDocs, Hugo, or a bare folder of Markdown files without requiring a specific plugin to be installed.
- i18n Navigation Integrity β Migrated project documentation from Folder Mode
(
docs/it/page.md) to Suffix Mode (docs/page.it.md). Suffix Mode eliminates asset-depth ambiguity: translated files are siblings of originals, so all relative link paths are symmetric between languages. Resolves context-loss during language switching and broken cross-locale asset resolution (double-slash 404s generated by absolute paths in folder-mode). - Asset Path Symmetry β Unified link depths for original and translated files. All
relative paths in
.it.mdfiles are now identical in structure to their.mdcounterparts, making translation maintenance straightforward and error-free.
- Portability Enforcement Layer β Pre-resolution stage added to
validate_links_asyncthat rejects absolute internal paths before theInMemoryPathResolveris consulted. Runs unconditionally regardless of engine, plugin, or locale configuration.
Note: This Release Candidate supersedes the yanked 0.3.0 stable tag and incorporates all Sprint 4 Phase 1 and Phase 2 work. It is the baseline for the v0.3.x line.
- Build-Aware Intelligence (i18n) β Zenzic now understands the MkDocs
i18nplugin infoldermode. Whenfallback_to_default: trueis set inmkdocs.yml, links to untranslated pages are resolved against the default locale before being reported as broken. No false positives for partial translations. excluded_build_artifactsβ newzenzic.tomlfield accepting glob patterns (e.g.["pdf/*.pdf"]) for assets generated at build time. Links to matching paths are suppressed at lint time without requiring the file to exist on disk.- Reference-style link validation β
[text][id]links are now resolved through the fullInMemoryPathResolverpipeline (including i18n fallback). Previously invisible to the link checker; now first-class citizens alongside inline links. I18nFallbackConfigβ internalNamedTupleencoding i18n fallback semantics (enabled,default_locale,locale_dirs). Designed for extension: any future locale-aware rule can consume this config without re-parsingmkdocs.yml.- Tower of Babel test suite (
tests/test_tower_of_babel.py) β 20 scenarios covering the full i18n folder-mode matrix: fully-translated pages, partial translations, ghost links, cross-locale direct links, case-sensitivity collisions, nested paths, orphan exclusion,ConfigurationErrorguards, and reference-style links across locales. - Engine-Agnostic Core β Zenzic is a pure standalone CLI, usable with any documentation framework (MkDocs, Zensical, or none). Zero plugin dependency.
InMemoryPathResolverβ deterministic, engine-agnostic link resolver inzenzic.core. Resolves internal Markdown links against a pre-built in-memory file map. Zero I/O after construction; supports relative, site-absolute, and fragment links.- Zenzic Shield β built-in protection against path traversal attacks during file scanning.
PathTraversalsurfaces as a distinct, high-severity outcome, not a generic "not found". - Hierarchical Configuration β new
fail_underfield inzenzic.toml(0β100) with precedence:--fail-underCLI flag >zenzic.toml> default0(observational mode). - Dynamic Scoring v2 β
zenzic score --savepersists aScoreReportJSON snapshot (.zenzic-score.json) withscore,threshold,status, and per-category breakdown, ready for shields.io badge automation viadynamic-badges-action. - Bilingual documentation β complete, synchronised EN/IT documentation across all sections.
- Orphan false positives β
find_orphans()no longer flags files inside locale subdirectories (e.g.docs/it/) as orphaned when the i18n plugin is configured infoldermode. - Non-deterministic asset validation β
validate_links_async()previously calledPath.exists()per link in the hot path, producing I/O-dependent results in CI. Pass 1 now builds aknown_assets: frozenset[str]pre-map; Pass 2 uses O(1) set membership with zero disk I/O. - Null-safe YAML iteration β
languages: nullinmkdocs.ymlis now handled correctly by all i18n helpers (or []guard pattern). Previously raisedTypeErrorwhen the key was present with a null value. - Entry Point β
pyproject.tomlcorrected tozenzic.main:cli_main, which initialises logging before handing off to Typer. Fixes missing log output on first invocation. - Type Safety β resolved
TypeError(MagicMock > int) in scorer tests caused by untyped config mock;mock_load.return_value = MagicMock(fail_under=0)now explicit in all affected tests. - Asset Integrity β build artifact generation (
.zip) automated inrun_demo.sh,nox -s preflight, and CI, ensuring a consistent 100/100 score across all entry points. BUILD_DATEtype coercion β format changed from%Y-%m-%dto%Y/%m/%dto prevent PyYAML from auto-converting the date string todatetime.date.- CVE-2026-4539 (Pygments ReDoS) β documented accepted risk:
AdlLexerReDoS in Pygments is non-reachable in Zenzic's threat model (Zenzic does not process ADL input; Pygments is used only for static documentation syntax highlighting). Exemption added tonox -s securitypending an upstream patch release. All other vulnerabilities remain fully audited.
- CLI Interface β removed all residual MkDocs plugin references; the public API is the
command-line interface only. Generator selection (
mkdocs.yml) is auto-detected at runtime. zenzic.tomlself-check βexcluded_build_artifacts = ["pdf/*.pdf"]added to the repository's own configuration, removing the requirement to pre-generate PDFs before runningzenzic check alllocally.- Zenzic Shield β Path Traversal protection now integrated into the
InMemoryPathResolvercore, replacing the prior ad-hoc check in the CLI wrapper.
Superseded by
0.3.0-rc1. This tag was cut before the Build-Aware Intelligence work (i18n folder-mode, O(1) asset mapping, reference-style links) was merged. Use0.3.0-rc1.
zensical.tomlsupport β Zensical now readsmkdocs.ymlnatively; a separatezensical.tomlis no longer required or supported as a build configuration file. Theexamples/broken-docs/zensical.tomlfixture is retained only as a test asset.mkdocsruntime dependency βmkdocs>=1.5.0removed from[project.dependencies]. MkDocs plugin packages (mkdocs-material,mkdocs-minify-plugin,mkdocs-with-pdf,mkdocstrings,mkdocs-static-i18n) remain in[dependency-groups.dev]pending native Zensical equivalents for the social, minify, and with-pdf features.- MkDocs plugin entry-point β
[project.entry-points."mkdocs.plugins"]removed. Zenzic no longer registers as amkdocs.pluginsentry-point. Usezenzic check allin CI instead.
find_config_file()β looks formkdocs.ymlonly;zensical.tomlpreference logic removed.find_repo_root()β walks up tomkdocs.ymlor.git; no longer checks forzensical.toml.find_orphans()β TOML-branch removed; always readsmkdocs.ymlvia_PermissiveYamlLoader. i18n locale-pattern fallback branch removed._detect_engine()β simplified:mkdocs.ymlis the single config trigger;zensicalis tried first (readsmkdocs.ymlnatively), thenmkdocs. Thezensical.toml-first heuristic is gone.noxfile.pyβdocsanddocs_servesessions usezensical build/serve;preflightuseszensical build --strict.justfileβbuild,serve, andbuild-releasetargets usezensical;liveis now an alias forserve.deploy-docs.ymlβ build step usesuv run zensical build --strict.zenzic.ymlβ path triggers pruned todocs/**andmkdocs.ymlonly.mkdocs.ymlβ version bumped to0.2.1; comment updated to note native Zensical reading.pyproject.tomlβ mypy override formkdocs.*removed (no longer a runtime dependency).
- Documentation restructure β new
docs/about/section withindex.md,vision.md,license.md(EN + IT); newdocs/reference/section withindex.mdandapi.md(EN + IT). Theapi-reference.mdfiles remain at the flat path for backwards compatibility but are now also served underreference/api.md. mkdocs.ymlnav β reflects the newabout/andreference/layout withnavigation.indexesandnavigation.expandMaterial features.
ReferenceMap(zenzic.models.references) β per-file stateful registry for[id]: urlreference link definitions. CommonMark Β§4.7 first-wins: the first definition of any ID in document order wins; subsequent definitions are ignored and tracked induplicate_ids. Keys are case-insensitive (lower().strip()). Each entry stores(url, line_no)metadata for precise error reports.integrity_scoreproperty returns|used_ids| / |definitions| Γ 100; guarded against ZeroDivisionError β returns100.0when no definitions exist.ReferenceScanner(zenzic.core.scanner) β stateful per-file scanner implementing a three-phase pipeline: (1) Harvesting (harvest()) streams lines via_iter_content_lines()generator (O(1) RAM per line), populatesReferenceMap, and runs the Zenzic Shield on every URL; (2) Cross-Check (cross_check()) resolves every[text][id]usage against the fully-populated map, emittingReferenceFinding(issue="DANGLING")for each Dangling Reference; (3) Integrity Report (get_integrity_report()) computesintegrity_score, surfaces Dead Definitions (issue="DEAD_DEF"), and consolidates all findings ordered errors-first.scan_docs_references/scan_docs_references_with_linksβ high-level orchestrators that run the pipeline over every.mdfile indocs/. Shield-as-firewall contract: Pass 2 (Cross-Check) is skipped entirely for any file withSECRETevents. Optional global URL deduplication viaLinkValidatorwhen--linksis requested.- Zenzic Shield (
zenzic.core.shield) β secret-detection engine that scans every reference URL during Harvesting using pre-compiled, exact-length quantifier patterns (no backtracking, O(1) per line). Three credential families: OpenAI API key (sk-[a-zA-Z0-9]{48}), GitHub token (gh[pousr]_[a-zA-Z0-9]{36}), AWS access key (AKIA[0-9A-Z]{16}). Any detection causes immediate abort with Exit Code 2; no HTTP requests are issued for documents containing leaked credentials. LinkValidator(zenzic.core.validator) β global URL deduplication registry across the entire docs tree.register_from_map()registers allhttp/httpsURLs from aReferenceMap.validate()issues exactly one HEAD request per unique URL regardless of how many files reference it. Reuses the existing_check_external_linksasync engine (semaphore(20), HEADβGET fallback, 401/403/429 treated as alive).zenzic check referencesCLI command β triggers the full Three-Phase Pipeline. Flags:--strict(Dead Definitions become hard errors),--links(async HTTP validation of all reference URLs, 1 ping per unique URL). Exit Code 2 reserved exclusively for Zenzic Shield events.- Alt-text accessibility check (
check_image_alt_text) β pure function flagging both inlineimages and HTML<img>tags without alt text.is_warning=True; promoted to errors under--strict. Never blocks deploys by default. zenzic.models.referencesβ new canonical module forReferenceMap,ReferenceFinding,IntegrityReport.zenzic.core.modelsbecomes a backwards-compatible re-export shim.
docs/architecture.mdβ "Two-Pass Reference Pipeline (v0.2.0)" section: statelessβdocument-aware comparison table, forward reference problem, lifecycle ASCII diagram, generator streaming rationale,ReferenceMapinvariants (first-wins, case-insensitivity, line-number metadata), Shield-as-firewall design, global URL deduplication diagram, accessibility nudge, full data flow summary with LaTeX integrity formula.docs/usage.mdβ complete v0.2.0 rewrite:uv/pipcontent tabs per installation tier,check referenceswith--strict/--links, Reference Integrity section with LaTeX formula, CI/CD integration section (uvxvsuv runtable, GitHub Actions workflow, exit code 2 handling), Programmatic Usage section withReferenceScannerAPI examples.README.mdβ reference link style throughout,## π‘οΈ Zenzic Shieldsection, exit code 2> [!WARNING], updated checks table.- All Italian locale counterparts (
*.it.md) synchronised per ParitΓ Documentale directive.
- Scanning model: stateless (line-by-line, no memory of prior lines) β document-aware (Three-Phase Pipeline with per-file
ReferenceMapstate). - Memory model:
_iter_content_lines()generator replaces.read()/.readlines()β peak RAM scales withReferenceMapsize, not file size. - Global URL deduplication extended to the reference pipeline:
LinkValidatordeduplicates at registration time across the entire docs tree β one HTTP request per unique URL regardless of reference count.
- Forward Reference Trap β single-pass scanners produce false Dangling Reference errors when
[text][id]appears before[id]: urlin the same file. Resolved by the Two-Pass design: Cross-Check runs only after Harvesting has fully populated theReferenceMap. - Reference ID normalisation: leading/trailing whitespace and mixed casing stripped inside
add_definition()andresolve()β duplicate entries for IDs differing only in case or spacing are impossible by construction.
- Exit Code 2 β reserved exclusively for Zenzic Shield events. If
zenzic check referencesexits with code 2, a credential pattern was detected embedded in a reference URL. The pipeline aborts immediately; all HTTP requests and Cross-Check analysis are skipped. Rotate the exposed credential immediately.
zenzic check linksβ fully rewritten as a native two-pass Markdown validator. Pass 1 reads all.mdfiles into memory and pre-computes anchor sets from ATX headings. Pass 2 extracts inline links and images via_MARKDOWN_LINK_RE, resolves internal paths against the in-memory file map, validates#fragmentanchors, and rejects path traversal outsidedocs/. Pass 3 (--strictonly) pings external URLs concurrently viahttpxwith bounded concurrency (asyncio.Semaphore(20)), URL deduplication, and graceful degradation for 401/403/429 responses. MkDocs is no longer required to run link validation; the check works with any MkDocs 1.x or Zensical project.
zenzic scoreβ aggregates all five check results into a weighted 0β100 integer. Weights:links35 %,orphans20 %,snippets20 %,placeholders15 %,assets10 %. Supports--format json,--save(persists snapshot to.zenzic-score.json), and--fail-under <n>(exits non-zero if score falls below threshold).zenzic diffβ compares the current score against the persisted.zenzic-score.jsonbaseline; exits non-zero when the score regresses beyond--thresholdpoints. Supports--format json.zenzic check all --exit-zeroβ produces the full five-check report but always exits with code 0; intended for soft-fail CI pipelines and active documentation improvement sprints.
zenzic serveβ auto-detects the documentation engine (zensicalormkdocs) from the repository root and launches it with--dev-addr 127.0.0.1:{port}. Falls back to a static file server onsite/when no engine binary is installed. Port resolution via socket probe before the subprocess starts (--port/-p, default8000, range1024β65535): if the requested port is busy, Zenzic probes up to ten consecutive ports and automatically selects the first free one βAddress already in useis eliminated at the engine level. Runs a silent pre-flight check (orphans, snippets, placeholders, unused assets) before server startup; issues are surfaced as warnings without blocking. Use--no-preflightto skip the quality check entirely.
- Auto-detects
mkdocs-i18nlocale suffixes: whenmkdocs.ymlconfigures thei18nplugin withdocs_structure: suffix,check orphansautomatically excludes*.{locale}.mdfiles for every non-default locale β zero configuration required, works for any number of languages.
excluded_assetsfield inZenzicConfigβ list of asset paths (relative todocs_dir) excluded from the unused-assets check; intended for files referenced bymkdocs.ymlor theme templates (favicons, logos, social preview images).excluded_file_patternsfield inZenzicConfigβ list of filename glob patterns excluded from the orphan check; escape hatch for locale schemes that cannot be auto-detected.
zenzic.core.scorerβ pure scoring engine (compute_score,save_snapshot,load_snapshot) decoupled from I/O; fully unit-tested.zenzic.core.exceptionsβ structured exception hierarchy:ZenzicError(base, carries optionalcontext: dict),ConfigurationError,EngineError,CheckError,NetworkError.zenzic.core.loggingβget_logger(name)andsetup_cli_logging(level)via standardlogging+RichHandler; plugin mode continues to uselogging.getLogger("mkdocs.plugins.zenzic")without interference.
- 98.4 % test coverage across
zenzic.core.*and CLI wrappers. - Async link-validation mocking β
_check_external_linksis exercised withpytest-asyncioandrespxtransport mocks; no outbound HTTP connections during CI. - Two-phase code-block isolation in
extract_links()β the link regex runs only on "safe" text. Phase 1a: a line-by-line state machine skips every line inside a fenced block (```/~~~). Phase 1b: inline code spans are replaced character-for-character with spaces viare.subbefore the regex runs. Links inside code examples are eliminated by construction; no regex backtracking, O(n) in file lines. PermissiveYamlLoaderinscanner.pyβ replacesyaml.safe_load, which was silently swallowing!ENVtags and leavingdoc_config = {}, causing every page to be reported as an orphan on projects that use environment-variable interpolation inmkdocs.yml.
- PyPI release via OIDC β
release.ymlworkflow publishes to PyPI using OpenID Connect trusted publishing; no long-lived API tokens stored in GitHub Secrets. - hatch build backend β
pyproject.tomlmigrated from setuptools tohatchling; version sourced from git tags viahatch-vcsfor reproducible builds. zensicalas optional extra βzensicaldependency moved tooptional-dependencies[zensical];pip install zenzicinstalls no documentation-generator binaries. Installzenzic[zensical]only on projects that use Zensical as the build driver.- Multi-language documentation (EN/IT) β all user-facing pages (
usage,checks,configuration,architecture) are available in English and Italian; locale suffix auto-detection ensures the orphan check passes on all locale variants. - Documentation overhaul β
docs/index.mdrewritten as a proper landing page with problem statement and "Where to go next" navigation;docs/usage.mdexpanded with scoring rationale, CI patterns, and plugin hook explanations;docs/checks.mdenriched with problem context and example output per check;docs/configuration.mdadds a "Getting started" section with zero-config examples;docs/architecture.mdupdated with the three-layer diagram, link extraction pipeline, two-phase isolation model, and async validation concurrency design. justfilerationalised β removedlint,check, andbuildtasks that duplicatednoxsessions;cleanno longer removes.nox/to preserve the cached virtualenv across runs.
_detect_engine()β--engineoverride now validates both binary presence on$PATHand required config file existence before returning;--engine zensicalacceptsmkdocs.ymlas a valid config for backwards compatibility (Zensical is a compatible successor to MkDocs); returnsNonewhen no config file is present, enabling the static-server fallback instead of raising an error.
zenzic buildβ generator build wrapper removed. It was a thin subprocess delegating entirely tomkdocs build/zensical build, adding no linting value and silently bypassing MkDocs plugins (i18n,social,minify,with-pdf) and Material theme configuration. Use your generator directly:mkdocs build,zensical build.detect_generator()andDocEnginefromzenzic.models.configβ replaced byfind_config_file()inzenzic.core.scanner, which returns the nav config path without generator-name or binary-availability metadata.
- Inline links and images only.
_MARKDOWN_LINK_REcaptures[text](url)andforms. Reference-style links ([text][id]) require a multi-pass, stateful parser (a per-file reference map) and are planned for v0.2.0. check snippets,check placeholders, andcheck assetsscan locale-suffixed files (e.g.,index.it.md) produced bymkdocs-i18n. This is intentional for snippet and placeholder validation but may produce duplicate asset-reference findings. Per-check exclusion viaexcluded_file_patternsscope is planned.
zenzic check linksβ strict broken-link and anchor detection viamkdocs build --strictzenzic check orphansβ detect.mdfiles missing fromnavzenzic check snippetsβ syntax-check all fenced Python code blockszenzic check placeholdersβ flag stub pages and forbidden placeholder textzenzic check assetsβ detect unused images and assetszenzic check allβ run all checks in a single command; supports--format jsonfor CI/CD integration- Professional PDF generation β integrated
with-pdfplugin with custom Jinja2 brand cover, PythonWoods gradient, and dynamic build timestamp zenzic.tomlconfiguration file with Pydantic v2 models; all fields optional with sane defaultsjustfileβ integrated task runner for rapid development (sync, lint, dev, build-release)examples/broken-docs/β intentionally broken documentation repo covering all five check typesnoxfile.pyβ developer task runner:tests,lint,format,typecheck,reuse,security,docs,preflight,screenshot,bumpscripts/generate_screenshot.pyβ reproducible SVG terminal screenshot via RichConsole(record=True)- Full REUSE 3.3 / SPDX compliance across all source files
- GitHub Actions β
ci.yml,release.yml,sbom.yml,secret-scan.yml,security-posture.yml,dependabot.yml - Documentation suite β index, architecture (using absolute
zenzic.corepaths), checks reference, and configuration reference - Pre-commit hooks β ruff, mypy, reuse, zenzic self-check