Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .bumpversion.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# SPDX-License-Identifier: Apache-2.0

[tool.bumpversion]
current_version = "0.26.1"
current_version = "0.26.2"
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)((?P<pre_l>a|b|rc)(?P<pre_n>\\d+))?"
serialize = [
"{major}.{minor}.{patch}{pre_l}{pre_n}",
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/security_vulnerability.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ body:
attributes:
label: Zenzic version
description: Output of `zenzic --version`
placeholder: "0.26.1"
placeholder: "0.26.2"
validations:
required: true

Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#
# repos:
# - repo: https://github.com/PythonWoods/zenzic
# rev: v0.26.1
# rev: v0.26.2
# hooks:
# - id: zenzic-verify # quality gate — corrisponde a `just verify` lato zenzic
# - id: zenzic-guard # fast staged-file credential scan
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,20 @@ Versions follow [Semantic Versioning](https://semver.org/).

## [Unreleased]

## [0.26.2] - 2026-07-28

### Fixed

- **Extensionless Asset Resolution**: Fixed a bug in `VSMBrokenLinkRule._to_canonical_url` where extensionless files (e.g., `LICENSE`, `Makefile`) incorrectly received a trailing slash when `use_directory_urls` was active, causing false-positive `Z101` findings.

## [0.26.1] - 2026-07-27

### Added

- **Adapter API Contract (`CORE-FIX-005`)**: Added the `use_directory_urls` property to the `BaseAdapter` contract. This allows adapters to explicitly declare their URL routing mode, eradicating encapsulation violations in the incremental engine.

### Fixed

- **URP Unification (`CORE-REFACTOR-003`)**: Eradicated the legacy CLI link validation pipeline (`validate_links_async`). Both CLI and LSP now evaluate broken internal links exclusively via `VSMBrokenLinkRule.check_vsm` and `PolyglotExtractor`, achieving 100% true validation parity.
- **Asset Indexing Parity (`CORE-REFACTOR-006`)**: Upgraded the Virtual Site Map (VSM) builder to explicitly index non-Markdown static assets (e.g., `.png`, `.webp`, `.html`). This eradicates hardcoded directory workarounds and eliminates false-positive `Z101` and `Z104` findings for static assets across all adapters.
- **JSON Purity (`CLI-FIX-001`)**: Enforced absolute JSON purity when the `--json` flag is active by routing `fail_under` and `suppression_cap` failure messages to `stderr`. This prevents `JSON.parse()` failures in programmatic consumers.
Expand Down
4 changes: 2 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ abstract: >-
performs deterministic static analysis using a two-pass reference
pipeline and a RE2-backed credential scanner, with zero subprocess
calls and full SARIF 2.1.0 support for CI/CD integration.
version: 0.26.1
date-released: 2026-07-27
version: 0.26.2
date-released: 2026-07-28
url: "https://zenzic.dev"
repository-code: "https://github.com/PythonWoods/zenzic"
repository-artifact: "https://pypi.org/project/zenzic/"
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ Zenzic Core is headless and emits standardized **SARIF** JSON, ensuring seamless
"tool": {
"driver": {
"name": "zenzic",
"version": "0.26.1",
"version": "0.26.2",
"rules": [
{
"id": "Z101",
Expand Down Expand Up @@ -215,7 +215,7 @@ uv tool upgrade zenzic
To run a specific version ephemerally without altering your global environment:

```bash
uvx zenzic@0.26.1 check all
uvx zenzic@0.26.2 check all
```

---
Expand Down
10 changes: 5 additions & 5 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

| Field | Value |
| :------- | :--------- |
| Version | v0.26.1 |
| Version | v0.26.2 |
| Codename | Magnetite |
| Date | 2026-07-27 |
| Date | 2026-07-28 |
| Status | Stable |

## Release Checklist
Expand All @@ -21,7 +21,7 @@ Before tagging, every item must be green:
- [ ] `zenzic lab all` — all 20 scenarios exit with expected code
- [ ] `zenzic score --stamp` committed — badge in README.md reflects current score
- [ ] `zenzic check all .` — zero findings in the repo root
- [ ] `pyproject.toml` version matches the tag (`0.26.1`)
- [ ] `pyproject.toml` version matches the tag (`0.26.2`)
- [ ] `CITATION.cff` version and date updated
- [ ] `CHANGELOG.md` — `[Unreleased]` section moved to the new version heading
- [ ] Update SECURITY.md support table (Add new release, demote previous to Critical/EOL).
Expand Down Expand Up @@ -53,12 +53,12 @@ git checkout main
git pull origin main

# 3. Tag the main branch and push
git tag -s -m "Release v0.26.1" v0.26.1
git tag -s -m "Release v0.26.2" v0.26.2
git push origin main --tags

```

- [ ] Create GitHub Release from the tag, using the `## [0.26.1]` CHANGELOG section as the release body.
- [ ] Create GitHub Release from the tag, using the `## [0.26.2]` CHANGELOG section as the release body.

## Changelog Reference

Expand Down
2 changes: 1 addition & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ extra:
# ADR-037: No hardcoded SemVer in any .html or .md source.
# CI pipeline passes the current version at build time, e.g.:
# uv run mkdocs build --extra zenzic_version=0.14.1
zenzic_version: "0.26.1" # release sync
zenzic_version: "0.26.2" # release sync
social:
- icon: fontawesome/brands/github
link: https://github.com/PythonWoods/zenzic
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ build-backend = "hatchling.build"

[project]
name = "zenzic"
version = "0.26.1"
version = "0.26.2"
description = "Deterministic Document Integrity Engine and SAST for Markdown/MDX graphs."
readme = "README.md"
requires-python = ">=3.10"
Expand Down
2 changes: 1 addition & 1 deletion src/zenzic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
# SPDX-License-Identifier: Apache-2.0
"""Zenzic — engine-agnostic static analyzer and credential scanner for Markdown documentation."""

__version__ = "0.26.1"
__version__ = "0.26.2"
__version_name__ = "Basalt" # Release codename stored separately from the package version.
17 changes: 15 additions & 2 deletions src/zenzic/cli/_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -1238,10 +1238,23 @@ def _rel(path: Path) -> str:
)
)
for rule_f in report.rule_findings:
if rule_f.rule_id in ("Z101", "Z102", "Z103", "Z104", "Z105", "Z106", "Z110", "Z120", "Z121", "Z122", "Z123", "Z124", "Z205"):
if rule_f.rule_id in (
"Z101",
"Z102",
"Z103",
"Z104",
"Z105",
"Z106",
"Z110",
"Z120",
"Z121",
"Z122",
"Z123",
"Z124",
"Z205",
):
continue
findings.append(

Finding(
rel_path=rel,
line_no=rule_f.line_no,
Expand Down
1 change: 1 addition & 0 deletions src/zenzic/cli/_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ def inspect_routes(

# ── Pass 1d: include static assets (HTML, webp, images, etc.) ──────────────
from zenzic.core.discovery import DOC_SUFFIXES, walk_files

static_assets: set[Path] = set()
if docs_root.is_dir():
for fpath in walk_files(docs_root, set(config.excluded_dirs), exclusion_mgr, config):
Expand Down
2 changes: 1 addition & 1 deletion src/zenzic/cli/_standalone.py
Original file line number Diff line number Diff line change
Expand Up @@ -1603,7 +1603,7 @@ def _scaffold_plugin(repo_root: Path, plugin_name: str, force: bool) -> None:
description = "Custom Zenzic plugin rule package"
readme = "README.md"
requires-python = ">=3.11"
dependencies = ["zenzic>=0.26.1"]
dependencies = ["zenzic>=0.26.2"]

[project.entry-points."zenzic.rules"]
{project_slug} = "{module_name}.rules:{class_name}"
Expand Down
1 change: 0 additions & 1 deletion src/zenzic/core/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,3 @@ class ExtractedLink:
col_start: int = 0
suppressed: bool = False
html_node: Any | None = None

37 changes: 23 additions & 14 deletions src/zenzic/core/incremental.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

import os
from pathlib import Path
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any
from urllib.parse import unquote, urlsplit
from urllib.request import url2pathname

Expand All @@ -41,7 +41,6 @@
AdaptiveRuleEngine,
ResolutionContext,
RuleFinding,
_extract_inline_links_with_lines,
)
from zenzic.core.suppressions import SuppressionTracker
from zenzic.core.validator import (
Expand Down Expand Up @@ -481,7 +480,6 @@ def _analyze_file(
self._run_urp_checks(vsm, path, text, tracker=tracker, extracted_links=extracted_links)
)


# Dead suppression detection
findings.extend(tracker.get_dead_suppressions())

Expand Down Expand Up @@ -566,7 +564,6 @@ def _run_urp_checks(
extracted_links: list[ExtractedLink] | None = None,
resolver: Any = None,
) -> list[RuleFinding]:

"""Run the Uniform Resolver Pipeline checks on a single file.

Covers: Z120, Z121, Z122, Z123, Z124, Z205, Z102, Z105, Z202, Z203.
Expand Down Expand Up @@ -761,18 +758,31 @@ def _source_line(lineno: int) -> str:
)
continue



# Non-markdown asset validation (Z104)
url_clean = url.split("?")[0].split("#")[0].lower()
is_asset = (
link.node_type in ("image", "html_img")
or any(url_clean.endswith(ext) for ext in (".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".ico", ".pdf", ".zip", ".tar.gz", ".html"))
is_asset = link.node_type in ("image", "html_img") or any(
url_clean.endswith(ext)
for ext in (
".png",
".jpg",
".jpeg",
".gif",
".webp",
".svg",
".ico",
".pdf",
".zip",
".tar.gz",
".html",
)
)
if is_asset:
if self.config.excluded_build_artifacts:
import fnmatch
if any(fnmatch.fnmatch(url, pat) for pat in self.config.excluded_build_artifacts):

if any(
fnmatch.fnmatch(url, pat) for pat in self.config.excluded_build_artifacts
):
continue

rel_url = unquote(parsed.path)
Expand All @@ -793,8 +803,6 @@ def _source_line(lineno: int) -> str:
)
continue



# Z102 (Local and Cross-file)
if parsed.fragment:
anchor = parsed.fragment.lower()
Expand Down Expand Up @@ -824,7 +832,9 @@ def _source_line(lineno: int) -> str:

if route is not None and anchor not in route.anchors:
# Check adapter i18n anchor fallback
if not self.adapter.resolve_anchor(target_path, anchor, self.anchors_cache, self.docs_root):
if not self.adapter.resolve_anchor(
target_path, anchor, self.anchors_cache, self.docs_root
):
findings.append(
RuleFinding(
path,
Expand All @@ -839,7 +849,6 @@ def _source_line(lineno: int) -> str:
return findings



# ── Module-level pure functions ───────────────────────────────────────────────


Expand Down
Loading
Loading