diff --git a/.bumpversion.toml b/.bumpversion.toml index c026b2d..2e19286 100644 --- a/.bumpversion.toml +++ b/.bumpversion.toml @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 [tool.bumpversion] -current_version = "0.9.0" +current_version = "0.9.1" parse = "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)((?Pa|b|rc)(?P\\d+))?" serialize = [ "{major}.{minor}.{patch}{pre_l}{pre_n}", @@ -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}" diff --git a/.github/ISSUE_TEMPLATE/security_vulnerability.yml b/.github/ISSUE_TEMPLATE/security_vulnerability.yml index ac6cbc4..27a2fc8 100644 --- a/.github/ISSUE_TEMPLATE/security_vulnerability.yml +++ b/.github/ISSUE_TEMPLATE/security_vulnerability.yml @@ -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 diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index bae8fc6..b90b3af 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -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 diff --git a/CITATION.cff b/CITATION.cff index 2f6d26c..5342f4d 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -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/" diff --git a/RELEASE.md b/RELEASE.md index 074785e..30057f2 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -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 @@ -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). @@ -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 diff --git a/examples/z102-anchor-missing/docs/index.md b/examples/z102-anchor-missing/docs/index.md index 44e15e3..52943b3 100644 --- a/examples/z102-anchor-missing/docs/index.md +++ b/examples/z102-anchor-missing/docs/index.md @@ -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. diff --git a/examples/z103-orphan-link/docs/index.md b/examples/z103-orphan-link/docs/index.md index 7a98c63..6d57ec0 100644 --- a/examples/z103-orphan-link/docs/index.md +++ b/examples/z103-orphan-link/docs/index.md @@ -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. diff --git a/examples/z105-absolute-path/docs/index.md b/examples/z105-absolute-path/docs/index.md index 7dd3d5d..44b5e85 100644 --- a/examples/z105-absolute-path/docs/index.md +++ b/examples/z105-absolute-path/docs/index.md @@ -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. diff --git a/examples/z108-empty-link-text/docs/index.md b/examples/z108-empty-link-text/docs/index.md index d928154..6dfe68e 100644 --- a/examples/z108-empty-link-text/docs/index.md +++ b/examples/z108-empty-link-text/docs/index.md @@ -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. diff --git a/examples/z201-credentials/docs/setup.md b/examples/z201-credentials/docs/setup.md index a0f22ff..fb5edf4 100644 --- a/examples/z201-credentials/docs/setup.md +++ b/examples/z201-credentials/docs/setup.md @@ -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. diff --git a/examples/z202-path-traversal/docs/index.md b/examples/z202-path-traversal/docs/index.md index 7759eb2..1034796 100644 --- a/examples/z202-path-traversal/docs/index.md +++ b/examples/z202-path-traversal/docs/index.md @@ -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. diff --git a/examples/z204-forbidden-term/docs/index.md b/examples/z204-forbidden-term/docs/index.md index 8c6fb7f..60b33e2 100644 --- a/examples/z204-forbidden-term/docs/index.md +++ b/examples/z204-forbidden-term/docs/index.md @@ -9,7 +9,7 @@ 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. @@ -17,7 +17,7 @@ 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". diff --git a/examples/z301-dangling-ref/docs/index.md b/examples/z301-dangling-ref/docs/index.md index 015fa24..fac1ed3 100644 --- a/examples/z301-dangling-ref/docs/index.md +++ b/examples/z301-dangling-ref/docs/index.md @@ -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. diff --git a/examples/z302-dead-def/README.md b/examples/z302-dead-def/README.md index 39bcc8b..b54635b 100644 --- a/examples/z302-dead-def/README.md +++ b/examples/z302-dead-def/README.md @@ -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**. diff --git a/examples/z303-duplicate-def/README.md b/examples/z303-duplicate-def/README.md index e658624..275815d 100644 --- a/examples/z303-duplicate-def/README.md +++ b/examples/z303-duplicate-def/README.md @@ -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**. diff --git a/examples/z303-duplicate-def/docs/index.md b/examples/z303-duplicate-def/docs/index.md index 9a4010e..19e97bf 100644 --- a/examples/z303-duplicate-def/docs/index.md +++ b/examples/z303-duplicate-def/docs/index.md @@ -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 - diff --git a/examples/z403-missing-alt/docs/diagram.png b/examples/z403-missing-alt/docs/diagram.png new file mode 100644 index 0000000..5806ced --- /dev/null +++ b/examples/z403-missing-alt/docs/diagram.png @@ -0,0 +1 @@ +dummy png content diff --git a/examples/z403-missing-alt/docs/index.md b/examples/z403-missing-alt/docs/index.md index 93f63ca..d8bde48 100644 --- a/examples/z403-missing-alt/docs/index.md +++ b/examples/z403-missing-alt/docs/index.md @@ -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. diff --git a/examples/z501-placeholder/docs/index.md b/examples/z501-placeholder/docs/index.md index 54bb4ff..f242c90 100644 --- a/examples/z501-placeholder/docs/index.md +++ b/examples/z501-placeholder/docs/index.md @@ -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. diff --git a/examples/z503-snippet-error/docs/index.md b/examples/z503-snippet-error/docs/index.md index 0e62b18..fb7529e 100644 --- a/examples/z503-snippet-error/docs/index.md +++ b/examples/z503-snippet-error/docs/index.md @@ -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 (, line 2) +docs/index.md:13: Z503 SNIPPET_ERROR Python block has a syntax error: invalid syntax (, line 2) ``` Run `zenzic check content` to reproduce the finding. diff --git a/examples/z505-untagged-code-block/docs/index.md b/examples/z505-untagged-code-block/docs/index.md index 62e13d2..bb7dcdd 100644 --- a/examples/z505-untagged-code-block/docs/index.md +++ b/examples/z505-untagged-code-block/docs/index.md @@ -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. diff --git a/examples/z601-brand-obsolescence/docs/guide.md b/examples/z601-brand-obsolescence/docs/guide.md new file mode 100644 index 0000000..f6befd3 --- /dev/null +++ b/examples/z601-brand-obsolescence/docs/guide.md @@ -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. diff --git a/examples/z602-i18n-parity/.zenzic.toml b/examples/z602-i18n-parity/.zenzic.toml index 487d949..da99dcd 100644 --- a/examples/z602-i18n-parity/.zenzic.toml +++ b/examples/z602-i18n-parity/.zenzic.toml @@ -20,3 +20,5 @@ locales = ["it"] [i18n] enabled = true +base_source = "docs/en" +targets = { it = "docs/it" } diff --git a/examples/z602-i18n-parity/docs/it/index.md b/examples/z602-i18n-parity/docs/it/index.md index 8b0fe02..4d2fdfc 100644 --- a/examples/z602-i18n-parity/docs/it/index.md +++ b/examples/z602-i18n-parity/docs/it/index.md @@ -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 diff --git a/pyproject.toml b/pyproject.toml index a6b98f7..5afaa0a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" diff --git a/src/zenzic/__init__.py b/src/zenzic/__init__.py index 2ea399f..b1da410 100644 --- a/src/zenzic/__init__.py +++ b/src/zenzic/__init__.py @@ -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. diff --git a/src/zenzic/cli/_standalone.py b/src/zenzic/cli/_standalone.py index 8f28d92..dc8ff3b 100644 --- a/src/zenzic/cli/_standalone.py +++ b/src/zenzic/cli/_standalone.py @@ -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}" diff --git a/src/zenzic/core/scanner.py b/src/zenzic/core/scanner.py index dfb8b35..475eaf7 100644 --- a/src/zenzic/core/scanner.py +++ b/src/zenzic/core/scanner.py @@ -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(): @@ -1078,7 +1081,7 @@ def check_image_alt_text( ) # HTML 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 @@ -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 ──────────────────────────────── @@ -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 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 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]) @@ -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, diff --git a/uv.lock b/uv.lock index 19135e5..9a5e253 100644 --- a/uv.lock +++ b/uv.lock @@ -2163,7 +2163,7 @@ wheels = [ [[package]] name = "zenzic" -version = "0.9.0" +version = "0.9.1" source = { editable = "." } dependencies = [ { name = "google-re2" },