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.10.2"
current_version = "0.10.3"
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.10.2"
placeholder: "0.10.3"
validations:
required: true

Expand Down
10 changes: 10 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ must satisfy all that apply.

---

## Enterprise governance compliance

- [ ] This PR addresses an approved Issue #___ and complies with the **Issue-First Policy**.
- [ ] Every commit in this PR is **cryptographically signed** (GPG/SSH/S/MIME) and shows as "Verified" on GitHub.
- [ ] Every commit has a valid **Developer Certificate of Origin (DCO)** sign-off (`Signed-off-by:` via `git commit -s`).
- [ ] I have verified and can architecturally justify every single line of code proposed in this PR (**No AI Slop**).
- [ ] All commit messages comply with the **Conventional Commits** specification.

---

## Quality gates

- [ ] `just verify` passes end-to-end (pre-commit + coverage ≥ 80% + `zenzic check all --strict` self-dogfood).
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,13 @@ jobs:

steps:
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

- name: Install just
uses: taiki-e/install-action@ea85faa6acd705ad6d40586db99f1a70b09c2929 # just

- name: Setup uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
enable-cache: true

Expand Down
48 changes: 48 additions & 0 deletions .github/workflows/compliance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# SPDX-FileCopyrightText: 2026 PythonWoods <dev@pythonwoods.dev>
# SPDX-License-Identifier: Apache-2.0

name: Zenzic Core Compliance

on:
pull_request:
types: [opened, edited, synchronize, reopened]

permissions:
contents: read
pull-requests: read

jobs:
pr-title:
name: Lint PR Title
runs-on: ubuntu-latest
steps:
- name: Validate PR Title
uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

dco:
name: Check DCO
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0

- name: Verify Signed-off-by
run: |
BASE_SHA="${{ github.event.pull_request.base.sha }}"
HEAD_SHA="${{ github.event.pull_request.head.sha }}"

echo "Checking commits between $BASE_SHA and $HEAD_SHA"

# Check each commit in the range
git log --no-merges --format='%H' "$BASE_SHA..$HEAD_SHA" | while read -r commit_sha; do
commit_msg=$(git log -1 --format='%B' "$commit_sha")
if ! echo "$commit_msg" | grep -q "^Signed-off-by:"; then
echo "::error::Commit $commit_sha is missing 'Signed-off-by:' sign-off."
exit 1
fi
done
echo "All commits have valid DCO sign-offs."
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
fetch-depth: 0

- name: Setup uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
enable-cache: true

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sbom.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:

steps:
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6

- name: Generate SBOM (Syft — spdx-json)
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/security-posture.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:

steps:
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6

- name: Check for SECURITY.md
run: |
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.10.2
# rev: v0.10.3
# hooks:
# - id: zenzic-verify # quality gate — corrisponde a `just verify` lato zenzic
# - id: zenzic-guard # fast staged-file credential scan
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@

---

## [0.10.3] - 2026-06-08

### Fixed

- **Core Engine (AST Parser):** Fixed Z104 false positives by correctly ignoring footnote definitions (e.g., `[^1]:`) in the AST reference builder.
- **Core Engine (AST Parser):** Fixed Z102 false positives by stripping markdown attribute lists (e.g., `{...}`) from headings before slugification and adding native support for explicit block-level and footnote anchors.
- **Core Engine (Snippet Validator):** Fixed Z503 false positives on MkDocs configurations and custom tags by registering PyYAML custom tags (e.g., `!!python/*`) and unregistered custom tags (e.g., `!ENV`) in the `PermissiveSafeLoader`.

---

## [0.10.2] - 2026-06-07

### Fixed
Expand Down Expand Up @@ -49,5 +59,5 @@
## Historical Releases

- v0.9.x archive: [changelogs/v0.9.md](./changelogs/v0.9.md)
- v0.8.x archive: [changelogs/v0.8.md](./changelogs/v0.8.md)

Check notice on line 62 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / Audit (ubuntu-latest, 3.14)

Z106

CHANGELOG.md:62: './changelogs/v0.8.md' is part of a circular link cycle

Check notice on line 62 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / Audit (ubuntu-latest, 3.10)

Z106

CHANGELOG.md:62: './changelogs/v0.8.md' is part of a circular link cycle
- v0.1.x–v0.7.x archive index: [changelogs/README.md](./changelogs/README.md)

Check notice on line 63 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / Audit (ubuntu-latest, 3.14)

Z106

CHANGELOG.md:63: './changelogs/README.md' is part of a circular link cycle

Check notice on line 63 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / Audit (ubuntu-latest, 3.10)

Z106

CHANGELOG.md:63: './changelogs/README.md' is part of a circular link cycle
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.10.2
date-released: 2026-06-07
version: 0.10.3
date-released: 2026-06-08
url: "https://zenzic.dev"
repository-code: "https://github.com/PythonWoods/zenzic"
repository-artifact: "https://pypi.org/project/zenzic/"
Expand Down
12 changes: 12 additions & 0 deletions CONTRIBUTING.it.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ model.

---

## Policy di Governance Enterprise e Contributo

Per garantire la sicurezza, l'integrità architetturale e la conformità legale di Zenzic, tutti i contributi devono aderire alle seguenti linee guida di Governance Enterprise:

1. **Issue-First Policy (Prima le Issue)**: Nessuna Pull Request sarà presa in carico, revisionata o discussa se non preceduta da una Issue corrispondente discussa e approvata dai maintainer. Collega sempre l'Issue approvata nella descrizione della tua PR.
2. **Firma Crittografica Obbligatoria**: Tutti i commit devono essere firmati crittograficamente tramite chiavi GPG, SSH o S/MIME (mostrati come "Verified" su GitHub). I commit non firmati verranno respinti automaticamente dal gate di merge.
3. **Clausola "No AI Slop"**: Applichiamo una policy severa contro il codice generato da intelligenza artificiale non verificato. I contributor devono comprendere appieno, saper spiegare e giustificare dal punto di vista architetturale ogni singola riga di codice proposta nella PR. La proposta di codice non compreso porterà al rifiuto immediato del contributo.
4. **Developer Certificate of Origin (DCO)**: Tutti i commit devono includere la riga `Signed-off-by:` (usando `git commit -s`) per certificare la conformità con la DCO.
5. **Conventional Commits**: I messaggi di commit devono seguire rigorosamente la specifica Conventional Commits (es. `feat: add block anchor support (#123)`).

---

## Prerequisiti

| Requisito | Versione | Note |
Expand Down
12 changes: 12 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ the live code registry and tier ownership model.

---

## Enterprise Governance & Contribution Policy

To maintain the security, architectural integrity, and legal compliance of Zenzic, all contributions must adhere to the following Enterprise Governance guidelines:

1. **Issue-First Policy**: No Pull Request will be reviewed, merged, or discussed unless it is preceded by a corresponding Issue that has been formally discussed and approved by the maintainers. Always link the approved Issue in your PR description.
2. **Mandatory Cryptographic Commit Signatures**: Every commit must be cryptographically signed using GPG, SSH, or S/MIME keypairs (appearing as "Verified" on GitHub). Unsigned commits will be rejected by the merge gates.
3. **No AI Slop Clause**: We enforce a strict policy against unverified AI-generated code. Contributors must fully understand, explain, and architecturally justify every single line of code proposed in a PR. Proposing code that you cannot explain will lead to immediate rejection of the contribution.
4. **Developer Certificate of Origin (DCO)**: All commits must include a `Signed-off-by:` line (using `git commit -s`) to certify compliance with the DCO.
5. **Conventional Commits**: Commit messages must strictly follow the Conventional Commits specification (e.g., `feat: add block anchor support (#123)`).

---

## Prerequisites

| Requirement | Version | Notes |
Expand Down
10 changes: 5 additions & 5 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
<!-- SPDX-License-Identifier: Apache-2.0 -->
# Release Procedure — Zenzic Core

> **[MAINTAINER SOP]** *This document contains the Standard Operating Procedure for Core Maintainers to cut and publish a new release. If you are an end-user looking for new features, please see the [CHANGELOG](./CHANGELOG.md).*

Check notice on line 5 in RELEASE.md

View workflow job for this annotation

GitHub Actions / Audit (ubuntu-latest, 3.14)

Z106

RELEASE.md:5: './CHANGELOG.md' is part of a circular link cycle

Check notice on line 5 in RELEASE.md

View workflow job for this annotation

GitHub Actions / Audit (ubuntu-latest, 3.10)

Z106

RELEASE.md:5: './CHANGELOG.md' is part of a circular link cycle

## Release Metadata

| Field | Value |
| :------- | :--------- |
| Version | v0.10.2 |
| Version | v0.10.3 |
| Codename | Magnetite |
| Date | 2026-06-07 |
| Date | 2026-06-08 |
| Status | Stable |

## Release Checklist
Expand All @@ -21,7 +21,7 @@
- [ ] `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.10.2`)
- [ ] `pyproject.toml` version matches the tag (`0.10.3`)
- [ ] `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 @@ -54,13 +54,13 @@
git pull origin main

# 3. Tag the main branch and push
git tag v0.10.2
git tag v0.10.3
git push origin main --tags
```

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

## Changelog Reference

For a detailed list of changes, see [CHANGELOG.md](./CHANGELOG.md).

Check notice on line 65 in RELEASE.md

View workflow job for this annotation

GitHub Actions / Audit (ubuntu-latest, 3.14)

Z106

RELEASE.md:65: './CHANGELOG.md' is part of a circular link cycle

Check notice on line 65 in RELEASE.md

View workflow job for this annotation

GitHub Actions / Audit (ubuntu-latest, 3.10)

Z106

RELEASE.md:65: './CHANGELOG.md' is part of a circular link cycle
For full history, see [Historical Archives](./changelogs/README.md).

Check notice on line 66 in RELEASE.md

View workflow job for this annotation

GitHub Actions / Audit (ubuntu-latest, 3.14)

Z106

RELEASE.md:66: './changelogs/README.md' is part of a circular link cycle

Check notice on line 66 in RELEASE.md

View workflow job for this annotation

GitHub Actions / Audit (ubuntu-latest, 3.10)

Z106

RELEASE.md:66: './changelogs/README.md' is part of a circular link cycle
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.10.2"
version = "0.10.3"
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.10.2"
__version__ = "0.10.3"
__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 @@ -1270,7 +1270,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.10.2"]
dependencies = ["zenzic>=0.10.3"]

[project.entry-points."zenzic.rules"]
{project_slug} = "{module_name}.rules:{class_name}"
Expand Down
59 changes: 50 additions & 9 deletions src/zenzic/core/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,18 @@ class _PermissiveSafeLoader(yaml.SafeLoader):
"""SafeLoader that silently ignores unknown YAML tags (e.g. MkDocs !ENV)."""


_PermissiveSafeLoader.add_multi_constructor("", lambda loader, tag_suffix, node: None) # type: ignore[no-untyped-call]
def _construct_undefined(loader: yaml.SafeLoader, tag_suffix: str, node: yaml.Node) -> Any:
if isinstance(node, yaml.ScalarNode):
return loader.construct_scalar(node)
elif isinstance(node, yaml.SequenceNode):
return loader.construct_sequence(node)
elif isinstance(node, yaml.MappingNode):
return loader.construct_mapping(node)
return None


_PermissiveSafeLoader.add_multi_constructor("!", _construct_undefined) # type: ignore[no-untyped-call]
_PermissiveSafeLoader.add_multi_constructor("tag:yaml.org,2002:python/", _construct_undefined) # type: ignore[no-untyped-call]


# ─── Regexes ──────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -112,6 +123,8 @@ class LinkInfo(NamedTuple):

# Matches MkDocs Material explicit anchor attribute: ``{ #custom-id }``
_EXPLICIT_ANCHOR_RE = re.compile(r"\{[^}]*#([\w-]+)[^}]*\}")
_ATTR_LIST_RE = re.compile(r"\s+\{[^}]*\}$")
_FN_DEF_RE = re.compile(r"^ {0,3}\[\^([^\]]+)\]:")

# Matches HTML tags to strip from heading text before slugification.
_HTML_TAG_RE = re.compile(r"<[^>]+>")
Expand Down Expand Up @@ -436,10 +449,11 @@ def slug_heading(heading: str) -> str:
"""
import unicodedata

explicit = _EXPLICIT_ANCHOR_RE.search(heading)
heading_clean = _ATTR_LIST_RE.sub("", heading).strip()
explicit = _EXPLICIT_ANCHOR_RE.search(heading_clean)
if explicit:
return explicit.group(1).lower()
slug = _HTML_TAG_RE.sub("", heading).strip()
slug = _HTML_TAG_RE.sub("", heading_clean).strip()
# Decompose accented characters and drop combining marks so that e.g.
# "Integrità" → "integrita" (matching MkDocs toc extension behaviour).
# Lowercase AFTER NFKD so that mathematical/styled Unicode codepoints
Expand All @@ -453,18 +467,42 @@ def slug_heading(heading: str) -> str:


def anchors_in_file(content: str) -> set[str]:
"""Return anchor slugs for every ATX heading in *content*.
"""Return anchor slugs for every ATX heading and custom/footnote anchor in *content*.

Recognises MkDocs Material explicit anchors (``{ #id }``) and strips HTML
tags from heading text before slugification.
Recognises MkDocs Material explicit anchors (``{ #id }``), block-level custom ID
anchors, footnote targets, and strips HTML tags from heading text before slugification.

Args:
content: Raw markdown content (no I/O).

Returns:
Set of lowercase anchor slugs, e.g. ``{'introduction', 'quick-start'}``.
"""
return {slug_heading(m.group(1)) for m in _HEADING_RE.finditer(content)}
anchors: set[str] = set()
# 1. Extract heading slugs
for m in _HEADING_RE.finditer(content):
anchors.add(slug_heading(m.group(1)))

# 2. Extract block-level explicit anchors & footnote anchors (skipping code blocks)
in_block = False
for line in content.splitlines():
stripped = line.strip()
if not in_block:
if stripped.startswith("```") or stripped.startswith("~~~"):
in_block = True
continue
# Search for explicit inline/block anchors { #id }
for m in _EXPLICIT_ANCHOR_RE.finditer(line):
anchors.add(m.group(1).lower())
# Search for footnote definitions [^label]:
fn_match = _FN_DEF_RE.match(line)
if fn_match:
label = fn_match.group(1).strip()
anchors.add(f"fn:{label}")
else:
if stripped.startswith("```") or stripped.startswith("~~~"):
in_block = False
return anchors


# ─── Reference link pure helpers (S4-4) ──────────────────────────────────────
Expand Down Expand Up @@ -493,7 +531,10 @@ def _build_ref_map(text: str) -> dict[str, str]:
continue
m = _REF_DEF_RE.match(line)
if m:
norm_id = m.group(1).lower().strip()
label = m.group(1)
if label.startswith("^"):
continue
norm_id = label.lower().strip()
if norm_id not in ref_map: # first-definition-wins
ref_map[norm_id] = m.group(2)
else:
Expand Down Expand Up @@ -1514,7 +1555,7 @@ def check_snippet_content(

elif lang in ("yaml", "yml"):
try:
list(yaml.safe_load_all(snippet))
list(yaml.load_all(snippet, Loader=_PermissiveSafeLoader))
except yaml.YAMLError as exc:
mark = getattr(exc, "problem_mark", None)
offset = (mark.line + 1) if mark is not None else 1
Expand Down
Loading
Loading