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
14 changes: 1 addition & 13 deletions .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.9.0"
current_version = "0.9.1"
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 Expand Up @@ -46,18 +46,6 @@ replace = 'dependencies = ["zenzic>={new_version}"]'
# Historical versioned documentation is tracked where explicit release
# markers are still present in the file body.

[[tool.bumpversion.files]]
filename = "CONTRIBUTING.md"
search = "v{current_version}"
replace = "v{new_version}"
serialize = ["{major}.{minor}.{patch}{pre_l}{pre_n}", "{major}.{minor}.{patch}"]

[[tool.bumpversion.files]]
filename = "CONTRIBUTING.it.md"
search = "v{current_version}"
replace = "v{new_version}"
serialize = ["{major}.{minor}.{patch}{pre_l}{pre_n}", "{major}.{minor}.{patch}"]

[[tool.bumpversion.files]]
filename = "RELEASE.md"
search = "{current_version}"
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.9.0"
placeholder: "0.9.1"
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.9.0
# rev: v0.9.1
# hooks:
# - id: zenzic-verify # quality gate — corrisponde a `just verify` lato zenzic
# - id: zenzic-guard # fast staged-file credential scan
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.9.0
date-released: 2026-05-31
version: 0.9.1
date-released: 2026-06-02
url: "https://zenzic.dev"
repository-code: "https://github.com/PythonWoods/zenzic"
repository-artifact: "https://pypi.org/project/zenzic/"
Expand Down
12 changes: 6 additions & 6 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

| Field | Value |
| :------- | :--------- |
| Version | v0.9.0 |
| Version | v0.9.1 |
| Codename | Basalt |
| Date | 2026-05-31 |
| Date | 2026-06-02 |
| Status | Stable |

## Release Checklist
Expand All @@ -19,7 +19,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 and README.it.md reflects current score
- [ ] `zenzic check all .` — zero findings in the repo root
- [ ] `pyproject.toml` version matches the tag (`0.9.0`)
- [ ] `pyproject.toml` version matches the tag (`0.9.1`)
- [ ] `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 All @@ -46,11 +46,11 @@ Distribution target: **PyPI** — `pip install zenzic` / `uvx zenzic`.
## Tag & Push

```bash
git tag v0.9.0
git push origin release/v0.9.0 --tags
git tag v0.9.1
git push origin release/v0.9.1 --tags
```

- [ ] Create GitHub Release from the tag, using the `## v0.9.0` CHANGELOG section as the release body.
- [ ] Create GitHub Release from the tag, using the `## v0.9.1` CHANGELOG section as the release body.

## Changelog Reference

Expand Down
2 changes: 1 addition & 1 deletion examples/z102-anchor-missing/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ demonstrating **Z102 ANCHOR_MISSING** detection.
## What Zenzic Reports

```text
docs/index.md:7: Z102 ANCHOR_MISSING guide.md#nonexistent-section — anchor not found on target page
docs/index.md:11: Z102 ANCHOR_MISSING guide.md#nonexistent-section — anchor not found on target page
```

Run `zenzic check links` to reproduce the finding.
2 changes: 1 addition & 1 deletion examples/z103-orphan-link/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The following link points to a page that exists on disk but has no nav entry:
## What Zenzic Reports

```text
docs/index.md:7: Z103 ORPHAN_LINK 'guide.md' exists but is not reachable via site navigation
docs/index.md:16: Z103 ORPHAN_LINK 'guide.md' exists but is not reachable via site navigation
```

Run `zenzic check links` to reproduce the finding.
2 changes: 1 addition & 1 deletion examples/z105-absolute-path/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ This page uses an absolute path link, demonstrating **Z105 ABSOLUTE_PATH** detec
## What Zenzic Reports

```text
docs/index.md:7: Z105 ABSOLUTE_PATH '/guide' — use a relative path
docs/index.md:10: Z105 ABSOLUTE_PATH '/guide' — use a relative path
```

Absolute paths break portability when a site is served from a subdirectory.
Expand Down
2 changes: 1 addition & 1 deletion examples/z108-empty-link-text/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ This page contains a link with an empty label, demonstrating **Z108 EMPTY_LINK_T
## What Zenzic Reports

```text
docs/index.md:7: Z108 EMPTY_LINK_TEXT link to 'guide.md' has no label
docs/index.md:10: Z108 EMPTY_LINK_TEXT link to 'guide.md' has no label
```

Run `zenzic check links` to reproduce the finding.
2 changes: 1 addition & 1 deletion examples/z201-credentials/docs/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ secret_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
```

> **Security note:** This is a documentation example demonstrating Z201
> CREDENTIAL_SECRET detection. Replace placeholder values with your actual
> CREDENTIAL_SECRET detection. Replace example values with your actual
> credentials via environment variables — never commit real keys to version
> control.

Expand Down
2 changes: 1 addition & 1 deletion examples/z202-path-traversal/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ demonstrating **Z202 PATH_TRAVERSAL** detection.
## What Zenzic Reports

```text
docs/index.md:7: Z202 PATH_TRAVERSAL '../../private/secret.txt' escapes the docs/ root boundary
docs/index.md:11: Z202 PATH_TRAVERSAL '../../private/secret.txt' escapes the docs/ root boundary
```

Z202 is non-suppressible. Exit code 2.
Expand Down
4 changes: 2 additions & 2 deletions examples/z204-forbidden-term/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ demonstrating **Z204 FORBIDDEN_TERM** detection.
## Forbidden Term Occurrence

The development team is building **ProjectX** — our internal codename for the
next-generation platform. This page was drafted before the public launch and
next-generation platform. This page was written before the public launch and
still contains the internal codename that must not appear in published docs.

The staging environment is available at `staging.internal.corp` for QA purposes.

## What Zenzic Reports

```text
docs/index.md:9: Z204 FORBIDDEN_TERM Forbidden term detected — remove from documentation: 'ProjectX'
docs/index.md:11: Z204 FORBIDDEN_TERM Forbidden term detected — remove from documentation: 'ProjectX'
```

Z204 is non-suppressible. Exit code 2. The CLI shows "POLICY VIOLATION DETECTED".
Expand Down
2 changes: 1 addition & 1 deletion examples/z301-dangling-ref/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ anywhere in this file — that is the intentional defect that triggers Z301.
## What Zenzic Reports

```text
docs/index.md:9: Z301 DANGLING_REF reference ID 'missing-ref' is used but never defined
docs/index.md:12: Z301 DANGLING_REF reference ID 'missing-ref' is used but never defined
```

Run `zenzic check references` to reproduce the finding.
2 changes: 1 addition & 1 deletion examples/z302-dead-def/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ zenzic check references
## Expected output

```text
docs/index.md:12: Z302 DEAD_DEF reference ID 'setup' defined but never used
docs/index.md:16: Z302 DEAD_DEF reference ID 'setup' defined but never used
```

Exit code **1**.
Expand Down
2 changes: 1 addition & 1 deletion examples/z303-duplicate-def/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ zenzic check references
## Expected output

```text
docs/index.md:13: Z303 DUPLICATE_DEF reference ID 'api' defined more than once
docs/index.md:16: Z303 DUPLICATE_DEF reference ID 'api' defined more than once
```

Exit code **1**.
Expand Down
1 change: 0 additions & 1 deletion examples/z303-duplicate-def/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,5 @@ See the [API documentation][api] for endpoint details.
The new [API][api] includes a breaking change in `/v2/auth`.

[api]: https://api-v1.example.com

<!-- The `api` reference ID is defined twice above — once for v1, once for v2.
That is the intentional defect that triggers Z303. -->
1 change: 1 addition & 0 deletions examples/z403-missing-alt/docs/diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion examples/z403-missing-alt/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The `![](diagram.png)` syntax above has an empty alt attribute → **Z403**.
## What Zenzic Reports

```text
docs/index.md:9: Z403 MISSING_ALT image 'diagram.png' has no alt text
docs/index.md:14: Z403 MISSING_ALT image 'diagram.png' has no alt text
```

Run `zenzic check assets` to reproduce the finding.
4 changes: 2 additions & 2 deletions examples/z501-placeholder/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ Coming soon!
## What Zenzic Reports

```text
docs/index.md:7: Z501 PLACEHOLDER placeholder pattern 'TODO:' matched
docs/index.md:11: Z501 PLACEHOLDER placeholder pattern 'Coming soon!' matched
docs/index.md:10: Z501 PLACEHOLDER placeholder pattern 'TODO:' matched
docs/index.md:16: Z501 PLACEHOLDER placeholder pattern 'Coming soon!' matched
```

Run `zenzic check content` to reproduce the findings.
2 changes: 1 addition & 1 deletion examples/z503-snippet-error/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ expression. Zenzic's `ast`-based validator catches this → **Z503**.
## What Zenzic Reports

```text
docs/index.md:10: Z503 SNIPPET_ERROR Python block has a syntax error: invalid syntax (<unknown>, line 2)
docs/index.md:13: Z503 SNIPPET_ERROR Python block has a syntax error: invalid syntax (<unknown>, line 2)
```

Run `zenzic check content` to reproduce the finding.
2 changes: 1 addition & 1 deletion examples/z505-untagged-code-block/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ just ` ``` ` without a language tag like `bash` or `text` → **Z505**.
## What Zenzic Reports

```text
docs/index.md:9: Z505 UNTAGGED_CODE_BLOCK fenced code block has no language specifier
docs/index.md:13: Z505 UNTAGGED_CODE_BLOCK fenced code block has no language specifier
```

Run `zenzic check content` to reproduce the finding.
3 changes: 3 additions & 0 deletions examples/z601-brand-obsolescence/docs/guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Guide

This is a comprehensive guide to getting started with our documentation platform. We will cover the installation process, configuration options, and basic usage details to ensure that you can get up and running as quickly as possible. Please read all sections carefully to understand the platform features and how to leverage them for your project requirements.
2 changes: 2 additions & 0 deletions examples/z602-i18n-parity/.zenzic.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ locales = ["it"]

[i18n]
enabled = true
base_source = "docs/en"
targets = { it = "docs/it" }
2 changes: 1 addition & 1 deletion examples/z602-i18n-parity/docs/it/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# Avvio rapido

Questo documento è la traduzione IT della pagina principale.
Questa pagina è la traduzione IT della pagina principale.
Il file `docs/it/guide.md` è **intenzionalmente assente** — Zenzic emette Z602.

## Installazione
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.9.0"
version = "0.9.1"
description = "Engineering-grade, engine-agnostic static analyzer and credential scanner for Markdown documentation"
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.9.0"
__version__ = "0.9.1"
__version_name__ = "Basalt" # Release codename stored separately from the package version.
2 changes: 1 addition & 1 deletion src/zenzic/cli/_standalone.py
Original file line number Diff line number Diff line change
Expand Up @@ -1323,7 +1323,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.9.0"]
dependencies = ["zenzic>=0.9.1"]

[project.entry-points."zenzic.rules"]
{project_slug} = "{module_name}.rules:{class_name}"
Expand Down
39 changes: 36 additions & 3 deletions src/zenzic/core/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -1062,8 +1062,11 @@ def check_image_alt_text(
findings: list[ReferenceFinding] = []

for lineno, line in enumerate(text.splitlines(), start=1):
# Blank out inline code to avoid false matches
clean = _INLINE_CODE_RE.sub(lambda m: " " * len(m.group()), line)

# Inline Markdown images
for m in _RE_IMAGE_INLINE.finditer(line):
for m in _RE_IMAGE_INLINE.finditer(clean):
alt_text = m.group(1)
url = m.group(2)
if not alt_text.strip():
Expand All @@ -1078,7 +1081,7 @@ def check_image_alt_text(
)

# HTML <img> tags
for img_match in _RE_HTML_IMG.finditer(line):
for img_match in _RE_HTML_IMG.finditer(clean):
tag = img_match.group()
alt_match = _RE_HTML_ALT.search(tag)
src = tag # fallback label when src is hard to extract
Expand Down Expand Up @@ -1123,6 +1126,7 @@ def __init__(self, file_path: Path, config: ZenzicConfig | None = None) -> None:
self.file_path = file_path
self.ref_map: ReferenceMap = ReferenceMap()
self._config = config or ZenzicConfig()
self.missing_alts: list[ReferenceFinding] = []

# ── Pass 1: Harvesting & Credential Scanner ────────────────────────────────

Expand Down Expand Up @@ -1210,13 +1214,40 @@ def harvest(self) -> Generator[HarvestEvent, None, None]:
continue

# ── Alt-text: inline images ───────────────────────────────────────
for img_match in _RE_IMAGE_INLINE.finditer(line):
clean = _INLINE_CODE_RE.sub(lambda m: " " * len(m.group()), line)
for img_match in _RE_IMAGE_INLINE.finditer(clean):
alt_text = img_match.group(1)
url = img_match.group(2)
if alt_text.strip():
content_events.append((lineno, "IMG", (alt_text, url)))
else:
content_events.append((lineno, "MISSING_ALT", url))
self.missing_alts.append(
ReferenceFinding(
file_path=self.file_path,
line_no=lineno,
issue="Z403",
detail=f"Image '{url}' has no alt text.",
is_warning=True,
)
)

# ── Alt-text: HTML <img> tags ─────────────────────────────────────
for img_match in _RE_HTML_IMG.finditer(clean):
tag = img_match.group()
alt_match = _RE_HTML_ALT.search(tag)
src = tag
if alt_match is None or not alt_match.group(1).strip():
content_events.append((lineno, "MISSING_ALT", src))
self.missing_alts.append(
ReferenceFinding(
file_path=self.file_path,
line_no=lineno,
issue="Z403",
detail=f"HTML <img> tag has no alt text: {src[:60]}",
is_warning=True,
)
)

# Yield all events in line-number order
yield from sorted(credential_events + content_events, key=lambda e: e[0])
Expand Down Expand Up @@ -1321,6 +1352,8 @@ def get_integrity_report(
)
)

findings.extend(self.missing_alts)

return IntegrityReport(
file_path=self.file_path,
score=self.ref_map.integrity_score,
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading