jekyll-theme-zer0 1.11.0 → 1.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/README.md +4 -4
- data/_data/backlog.yml +115 -2
- data/_includes/components/theme-preview-gallery.html +1 -1
- data/scripts/docs/check-freshness.sh +123 -0
- data/scripts/docs/check-links.sh +123 -0
- data/scripts/docs/lint-frontmatter.sh +197 -0
- data/scripts/docs/validate.sh +90 -0
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9501bcc820ec7514f9b42ebe63e0541de0287933c388facb2b6eaec3bac2e4e3
|
|
4
|
+
data.tar.gz: 4d756e820a18743a6a7fc391b52fd40fa606493d60d2854d2d377cdede74d496
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b617c07bb328bcebf4be86fd0fdd8a3bc3cd513cdcf5fb6e6a1ebb564e39f318e245411a382eceeec0746549f6eb40b177682167f488a6b450e7d073042b9cb2
|
|
7
|
+
data.tar.gz: 1fab63085e0fc3be064120316e61b840c705e551e19c08a7329dc2290b682bbbb475e935917822c1538c9ca76b0a9ee7aa730e4cd5973d1e45a92addda390a40
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.11.2] - 2026-06-03
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- Version bump: patch release
|
|
7
|
+
|
|
8
|
+
### Commits in this release
|
|
9
|
+
- 452022ae chore(backlog): audit 2026-06-01 (#121)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## [1.11.1] - 2026-06-01
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- Version bump: patch release
|
|
16
|
+
|
|
17
|
+
### Commits in this release
|
|
18
|
+
- d4a53d51 docs: consolidate, standardize, and add maintenance system (#112)
|
|
19
|
+
|
|
20
|
+
|
|
3
21
|
## [1.11.0] - 2026-06-01
|
|
4
22
|
|
|
5
23
|
### Changed
|
data/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
title: zer0-mistakes
|
|
3
3
|
sub-title: AI-Native Jekyll Theme
|
|
4
4
|
description: AI-native Jekyll theme for GitHub Pages — Docker-first development, AI-powered installation, multi-agent integration (Copilot, Codex, Cursor, Claude), AI preview-image generation, and AIEO content optimization with Bootstrap 5.3.
|
|
5
|
-
version: 1.11.
|
|
5
|
+
version: 1.11.2
|
|
6
6
|
layout: landing
|
|
7
7
|
tags:
|
|
8
8
|
- jekyll
|
|
@@ -20,7 +20,7 @@ categories:
|
|
|
20
20
|
- bootstrap
|
|
21
21
|
- ai-tooling
|
|
22
22
|
created: 2024-02-10T23:51:11.480Z
|
|
23
|
-
lastmod: 2026-06-
|
|
23
|
+
lastmod: 2026-06-03T01:19:09.000Z
|
|
24
24
|
draft: false
|
|
25
25
|
permalink: /
|
|
26
26
|
slug: zer0
|
|
@@ -901,7 +901,7 @@ git push origin feature/awesome-feature
|
|
|
901
901
|
|
|
902
902
|
| Metric | Value |
|
|
903
903
|
|--------|-------|
|
|
904
|
-
| **Current Version** | 1.11.
|
|
904
|
+
| **Current Version** | 1.11.2 ([RubyGems](https://rubygems.org/gems/jekyll-theme-zer0), [CHANGELOG](/CHANGELOG)) |
|
|
905
905
|
| **Documented Features** | 43 ([Feature Registry](https://github.com/bamr87/zer0-mistakes/blob/main/_data/features.yml)) |
|
|
906
906
|
| **Setup Time** | 2-5 minutes ([install.sh benchmarks](https://github.com/bamr87/zer0-mistakes/blob/main/install.sh)) |
|
|
907
907
|
| **Documentation Pages** | 70+ ([browse docs](https://zer0-mistakes.com/pages/)) |
|
|
@@ -956,6 +956,6 @@ And these AI partners that make zer0-mistakes truly AI-native:
|
|
|
956
956
|
|
|
957
957
|
**Built with ❤️ — and a little help from our AI partners — for the Jekyll community**
|
|
958
958
|
|
|
959
|
-
**v1.11.
|
|
959
|
+
**v1.11.2** • [Changelog](CHANGELOG.md) • [License](LICENSE) • [Contributing](CONTRIBUTING.md) • [AI Agent Guide](AGENTS.md)
|
|
960
960
|
|
|
961
961
|
|
data/_data/backlog.yml
CHANGED
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
|
|
56
56
|
meta:
|
|
57
57
|
title: "zer0-mistakes Backlog"
|
|
58
|
-
updated: 2026-
|
|
59
|
-
next_id:
|
|
58
|
+
updated: 2026-06-01
|
|
59
|
+
next_id: 12
|
|
60
60
|
|
|
61
61
|
tasks:
|
|
62
62
|
# --- Housekeeping (seeded so the loop has work on day one) ------------------
|
|
@@ -159,3 +159,116 @@ tasks:
|
|
|
159
159
|
links: { issue: null, pr: null, roadmap: "1.0" }
|
|
160
160
|
created: 2026-05-31
|
|
161
161
|
updated: 2026-05-31
|
|
162
|
+
|
|
163
|
+
# --- 2026-06-01 audit -------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
- id: T-007
|
|
166
|
+
title: "Fix global navbar WCAG 2.1 AA violations to enable axe-core full-audit tests"
|
|
167
|
+
status: open
|
|
168
|
+
priority: P1
|
|
169
|
+
area: a11y
|
|
170
|
+
risk: standard
|
|
171
|
+
effort: M
|
|
172
|
+
source: audit
|
|
173
|
+
summary: >-
|
|
174
|
+
Three tests in `test/visual/accessibility.spec.js` are frozen with `test.fixme`
|
|
175
|
+
because four WCAG 2.1 AA violations in the global navbar prevent them from passing:
|
|
176
|
+
`aria-required-children` (menubar/button nesting), `link-name` (icon-only nav links),
|
|
177
|
+
`list` (footer list structure), and `scrollable-region-focusable` (code blocks).
|
|
178
|
+
Fix the HTML/ARIA in `_includes/` and `_layouts/` to make the full axe-core audit green.
|
|
179
|
+
acceptance:
|
|
180
|
+
- "All four violations listed in the `test.fixme` comment are resolved in `_includes/` / `_layouts/`."
|
|
181
|
+
- "The three `test.fixme` blocks in `test/visual/accessibility.spec.js` are changed to live `test()` calls and pass in CI."
|
|
182
|
+
- "`bundle exec ./scripts/bin/validate --quick` still passes."
|
|
183
|
+
- "No visual regression in smoke snapshot tests."
|
|
184
|
+
links: { issue: null, pr: null, roadmap: "1.9" }
|
|
185
|
+
created: 2026-06-01
|
|
186
|
+
updated: 2026-06-01
|
|
187
|
+
|
|
188
|
+
- id: T-008
|
|
189
|
+
title: "Fix theme-customizer YAML export to quote hex color values"
|
|
190
|
+
status: open
|
|
191
|
+
priority: P2
|
|
192
|
+
area: feat
|
|
193
|
+
risk: standard
|
|
194
|
+
effort: S
|
|
195
|
+
source: audit
|
|
196
|
+
summary: >-
|
|
197
|
+
The JS theme-customizer export serialises CSS custom-property values directly into
|
|
198
|
+
YAML without quoting strings that start with `#`. Unquoted `#RRGGBB` is treated as a
|
|
199
|
+
YAML comment by parsers, silently dropping colour values. A regression guard in
|
|
200
|
+
`test/visual/theme-colors.spec.js` is frozen as `test.fixme` until the bug is fixed.
|
|
201
|
+
acceptance:
|
|
202
|
+
- "JS export wraps every hex colour value (matching `/#[0-9a-fA-F]{3,8}/`) in double quotes in the generated YAML."
|
|
203
|
+
- "The `test.fixme` in `test/visual/theme-colors.spec.js` is promoted to a live `test()` and passes in CI."
|
|
204
|
+
- "Exported YAML is valid when parsed by `yaml.safe_load` in Ruby."
|
|
205
|
+
links: { issue: null, pr: null, roadmap: null }
|
|
206
|
+
created: 2026-06-01
|
|
207
|
+
updated: 2026-06-01
|
|
208
|
+
|
|
209
|
+
- id: T-009
|
|
210
|
+
title: "Sanitize sensitive config keys from admin config-page DOM injection"
|
|
211
|
+
status: open
|
|
212
|
+
priority: P1
|
|
213
|
+
area: infra
|
|
214
|
+
risk: standard
|
|
215
|
+
effort: S
|
|
216
|
+
source: audit
|
|
217
|
+
summary: >-
|
|
218
|
+
The admin config page injects raw `_config.yml` content into a hidden
|
|
219
|
+
`<pre id="cfg-full-yaml">` element. If the config contains secrets
|
|
220
|
+
(`api_key`, `token`, PostHog `phc_*` keys, etc.) they are exposed in the page
|
|
221
|
+
DOM. A regression test in `test/visual/security.spec.js` is frozen as
|
|
222
|
+
`test.fixme` until a sanitisation pass is in place.
|
|
223
|
+
acceptance:
|
|
224
|
+
- "The Liquid/Ruby that populates `<pre id=\"cfg-full-yaml\">` strips or masks keys matching `api_key`, `secret`, `password`, `token`, and `phc_` prefix before DOM injection."
|
|
225
|
+
- "The `test.fixme` in `test/visual/security.spec.js` is promoted to a live `test()` and passes in CI."
|
|
226
|
+
- "The visible config display in the admin UI is unaffected (only the raw hidden element is sanitised)."
|
|
227
|
+
links: { issue: null, pr: null, roadmap: null }
|
|
228
|
+
created: 2026-06-01
|
|
229
|
+
updated: 2026-06-01
|
|
230
|
+
|
|
231
|
+
- id: T-010
|
|
232
|
+
title: "Complete v1.9 quickstart docs rewrite with getting-started guide and screenshots"
|
|
233
|
+
status: open
|
|
234
|
+
priority: P2
|
|
235
|
+
area: docs
|
|
236
|
+
risk: low
|
|
237
|
+
effort: M
|
|
238
|
+
source: roadmap
|
|
239
|
+
summary: >-
|
|
240
|
+
The v1.9 milestone lists "Quickstart documentation rewrite with screenshots"
|
|
241
|
+
as a deliverable, but `pages/_docs/quickstart/` contains only `bare-minimum.md`.
|
|
242
|
+
Add a proper quickstart index page and at least one full getting-started guide
|
|
243
|
+
(covering the standard GitHub Pages workflow) with annotated screenshots.
|
|
244
|
+
acceptance:
|
|
245
|
+
- "`pages/_docs/quickstart/index.md` exists with a nav overview linking to available quickstart guides."
|
|
246
|
+
- "At least one guide beyond `bare-minimum.md` covers the standard fork/remote-theme workflow."
|
|
247
|
+
- "Each guide includes at least one screenshot stored under `assets/images/docs/quickstart/`."
|
|
248
|
+
- "All new pages pass `docs-validate.yml` front-matter and link checks."
|
|
249
|
+
links: { issue: null, pr: null, roadmap: "1.9" }
|
|
250
|
+
created: 2026-06-01
|
|
251
|
+
updated: 2026-06-01
|
|
252
|
+
|
|
253
|
+
- id: T-011
|
|
254
|
+
title: "Add Ruby unit tests for uncovered _plugins/ generators"
|
|
255
|
+
status: open
|
|
256
|
+
priority: P2
|
|
257
|
+
area: tests
|
|
258
|
+
risk: standard
|
|
259
|
+
effort: M
|
|
260
|
+
source: audit
|
|
261
|
+
summary: >-
|
|
262
|
+
Three Jekyll plugins have no dedicated unit tests: `content_statistics_generator.rb`,
|
|
263
|
+
`preview_image_generator.rb`, and `admin_page_urls.rb`. The quality suite has
|
|
264
|
+
URL-pattern checks (`test_preview_image_urls`) but no specs that exercise the
|
|
265
|
+
generator logic in isolation. This is a concrete gap on the path to the
|
|
266
|
+
3.0-milestone 90% coverage target.
|
|
267
|
+
acceptance:
|
|
268
|
+
- "`test/test_ruby_converter.rb`-style RSpec or Minitest specs exist for all three plugins."
|
|
269
|
+
- "Each spec runs independently without a running Jekyll server (`bundle exec ruby -Itest`)."
|
|
270
|
+
- "`./scripts/bin/test` continues to pass with the new specs included."
|
|
271
|
+
- "Edge cases covered: empty collections, missing front-matter keys, and duplicate slug detection."
|
|
272
|
+
links: { issue: null, pr: null, roadmap: "3.0" }
|
|
273
|
+
created: 2026-06-01
|
|
274
|
+
updated: 2026-06-01
|
|
@@ -442,7 +442,7 @@ docker-compose up
|
|
|
442
442
|
<div class="card-body d-flex flex-column gap-4">
|
|
443
443
|
<div>
|
|
444
444
|
<p class="small text-body-secondary mb-1">Breadcrumb</p>
|
|
445
|
-
<nav aria-label="
|
|
445
|
+
<nav aria-label="Breadcrumb example">
|
|
446
446
|
<ol class="breadcrumb mb-0">
|
|
447
447
|
<li class="breadcrumb-item"><a href="#">Home</a></li>
|
|
448
448
|
<li class="breadcrumb-item"><a href="#">Settings</a></li>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# =========================================================================
|
|
3
|
+
# scripts/docs/check-freshness.sh — Staleness detector for docs/
|
|
4
|
+
# =========================================================================
|
|
5
|
+
# Flags docs where the lastmod front matter field is more than THRESHOLD
|
|
6
|
+
# days behind the file's most recent git commit. This surfaces docs that
|
|
7
|
+
# describe code that has since changed without a corresponding doc update.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# ./scripts/docs/check-freshness.sh
|
|
11
|
+
# ./scripts/docs/check-freshness.sh --threshold 90 # custom days (default: 60)
|
|
12
|
+
# ./scripts/docs/check-freshness.sh --verbose
|
|
13
|
+
# =========================================================================
|
|
14
|
+
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
18
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
19
|
+
source "$SCRIPT_DIR/../lib/common.sh"
|
|
20
|
+
source "$SCRIPT_DIR/../lib/frontmatter.sh"
|
|
21
|
+
|
|
22
|
+
THRESHOLD_DAYS=60
|
|
23
|
+
STALE_COUNT=0
|
|
24
|
+
FILES_CHECKED=0
|
|
25
|
+
STALE_FILES=""
|
|
26
|
+
|
|
27
|
+
parse_args() {
|
|
28
|
+
while [[ $# -gt 0 ]]; do
|
|
29
|
+
case "$1" in
|
|
30
|
+
--threshold) shift; THRESHOLD_DAYS="$1" ;;
|
|
31
|
+
--verbose) export VERBOSE=true ;;
|
|
32
|
+
--help|-h) show_usage; exit 0 ;;
|
|
33
|
+
*) warn "Unknown option: $1" ;;
|
|
34
|
+
esac
|
|
35
|
+
shift
|
|
36
|
+
done
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
show_usage() {
|
|
40
|
+
cat << EOF
|
|
41
|
+
Freshness Checker for docs/
|
|
42
|
+
|
|
43
|
+
USAGE:
|
|
44
|
+
./scripts/docs/check-freshness.sh [OPTIONS]
|
|
45
|
+
|
|
46
|
+
OPTIONS:
|
|
47
|
+
--threshold N Days of drift before flagging as stale (default: $THRESHOLD_DAYS)
|
|
48
|
+
--verbose Show all files, not just stale ones
|
|
49
|
+
|
|
50
|
+
A doc is stale when:
|
|
51
|
+
git log -1 date for that file > (lastmod front matter + THRESHOLD days)
|
|
52
|
+
|
|
53
|
+
This catches docs describing code that changed but whose lastmod wasn't updated.
|
|
54
|
+
EOF
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Returns seconds since epoch for a date string, or empty on failure
|
|
58
|
+
date_to_epoch() {
|
|
59
|
+
local d="$1"
|
|
60
|
+
ruby -rtime -e "puts Time.parse('$d').to_i" 2>/dev/null || true
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
parse_args "$@"
|
|
64
|
+
|
|
65
|
+
THRESHOLD_SECS=$(( THRESHOLD_DAYS * 86400 ))
|
|
66
|
+
|
|
67
|
+
log "Checking freshness of docs (threshold: ${THRESHOLD_DAYS} days)..."
|
|
68
|
+
echo ""
|
|
69
|
+
|
|
70
|
+
for filepath in $(find "$REPO_ROOT/docs" -name "*.md" -not -name "README.md" -not -path "*/archive/*" | sort); do
|
|
71
|
+
relpath="${filepath#$REPO_ROOT/}"
|
|
72
|
+
|
|
73
|
+
# Skip files without front matter
|
|
74
|
+
local_first=""
|
|
75
|
+
IFS= read -r local_first < "$filepath" || true
|
|
76
|
+
if [[ ! "$local_first" =~ ^--- ]]; then
|
|
77
|
+
debug " SKIP (no front matter): $relpath"
|
|
78
|
+
continue
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
lastmod_str="$(get_frontmatter_field "$filepath" "lastmod" 2>/dev/null || true)"
|
|
82
|
+
if [[ -z "$lastmod_str" ]]; then
|
|
83
|
+
debug " SKIP (no lastmod field): $relpath"
|
|
84
|
+
continue
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
lastmod_epoch="$(date_to_epoch "$lastmod_str")"
|
|
88
|
+
[[ -z "$lastmod_epoch" ]] && continue
|
|
89
|
+
|
|
90
|
+
# Get last git commit date for this file
|
|
91
|
+
git_date_str="$(git -C "$REPO_ROOT" log -1 --format="%aI" -- "$relpath" 2>/dev/null || true)"
|
|
92
|
+
[[ -z "$git_date_str" ]] && continue
|
|
93
|
+
|
|
94
|
+
git_epoch="$(date_to_epoch "$git_date_str")"
|
|
95
|
+
[[ -z "$git_epoch" ]] && continue
|
|
96
|
+
|
|
97
|
+
drift=$(( git_epoch - lastmod_epoch ))
|
|
98
|
+
|
|
99
|
+
if [[ $drift -gt $THRESHOLD_SECS ]]; then
|
|
100
|
+
drift_days=$(( drift / 86400 ))
|
|
101
|
+
warn " STALE (${drift_days}d behind): $relpath"
|
|
102
|
+
warn " lastmod: $lastmod_str"
|
|
103
|
+
warn " git: $git_date_str"
|
|
104
|
+
STALE_FILES="$STALE_FILES $relpath"
|
|
105
|
+
STALE_COUNT=$((STALE_COUNT + 1))
|
|
106
|
+
else
|
|
107
|
+
debug " FRESH: $relpath"
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
FILES_CHECKED=$((FILES_CHECKED + 1))
|
|
111
|
+
done
|
|
112
|
+
|
|
113
|
+
echo ""
|
|
114
|
+
log "Results: $FILES_CHECKED files checked, $STALE_COUNT stale"
|
|
115
|
+
|
|
116
|
+
if [[ $STALE_COUNT -gt 0 ]]; then
|
|
117
|
+
echo ""
|
|
118
|
+
info "Stale files (update lastmod after reviewing content):"
|
|
119
|
+
for f in $STALE_FILES; do
|
|
120
|
+
echo " - $f"
|
|
121
|
+
done
|
|
122
|
+
exit 1
|
|
123
|
+
fi
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# =========================================================================
|
|
3
|
+
# scripts/docs/check-links.sh — Internal link checker for docs/
|
|
4
|
+
# =========================================================================
|
|
5
|
+
# Validates that relative markdown links in docs/**/*.md resolve to files
|
|
6
|
+
# that actually exist in the repository. Does not validate external URLs.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# ./scripts/docs/check-links.sh
|
|
10
|
+
# ./scripts/docs/check-links.sh --verbose
|
|
11
|
+
# =========================================================================
|
|
12
|
+
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
16
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
17
|
+
source "$SCRIPT_DIR/../lib/common.sh"
|
|
18
|
+
|
|
19
|
+
ERRORS=0
|
|
20
|
+
FILES_CHECKED=0
|
|
21
|
+
|
|
22
|
+
parse_args() {
|
|
23
|
+
while [[ $# -gt 0 ]]; do
|
|
24
|
+
case "$1" in
|
|
25
|
+
--verbose) export VERBOSE=true ;;
|
|
26
|
+
--help|-h) show_usage; exit 0 ;;
|
|
27
|
+
*) warn "Unknown option: $1" ;;
|
|
28
|
+
esac
|
|
29
|
+
shift
|
|
30
|
+
done
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
show_usage() {
|
|
34
|
+
cat << 'EOF'
|
|
35
|
+
Internal Link Checker for docs/
|
|
36
|
+
|
|
37
|
+
USAGE:
|
|
38
|
+
./scripts/docs/check-links.sh [--verbose]
|
|
39
|
+
|
|
40
|
+
Checks relative markdown links ([text](path)) in docs/**/*.md to verify
|
|
41
|
+
the target file exists. The contributor docs under docs/ use filesystem
|
|
42
|
+
relative links between markdown files, so they can be resolved on disk.
|
|
43
|
+
|
|
44
|
+
Not covered here:
|
|
45
|
+
- pages/_docs/**: the user-facing Jekyll site uses permalink URLs and
|
|
46
|
+
the relative_url filter, which are not filesystem paths; those links
|
|
47
|
+
are validated by htmlproofer against the built _site in CI.
|
|
48
|
+
- External URLs (http://, https://, mailto:, etc.)
|
|
49
|
+
- Anchor-only links (#section)
|
|
50
|
+
- Absolute site URLs (/docs/..., /faq/, ...)
|
|
51
|
+
EOF
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Extract relative markdown links from a file, excluding external, anchor-only,
|
|
55
|
+
# and absolute site URLs. Uses Ruby (already required by this repo) so the
|
|
56
|
+
# extraction is portable across GNU and BSD/macOS environments.
|
|
57
|
+
extract_relative_links() {
|
|
58
|
+
local filepath="$1"
|
|
59
|
+
ruby -e '
|
|
60
|
+
in_fence = false
|
|
61
|
+
File.foreach(ARGV[0], encoding: "UTF-8") do |line|
|
|
62
|
+
# Toggle fenced code blocks (``` or ~~~); skip their contents,
|
|
63
|
+
# which are examples rather than real links.
|
|
64
|
+
if line =~ /\A\s*(```|~~~)/
|
|
65
|
+
in_fence = !in_fence
|
|
66
|
+
next
|
|
67
|
+
end
|
|
68
|
+
next if in_fence
|
|
69
|
+
# Ignore inline code spans so `[x](y)` written as code is skipped.
|
|
70
|
+
scrubbed = line.gsub(/`[^`]*`/, "")
|
|
71
|
+
scrubbed.scan(/\]\(([^)]+)\)/) do |m|
|
|
72
|
+
link = m[0].strip
|
|
73
|
+
next if link.empty?
|
|
74
|
+
next if link.include?("{{") || link.include?("{%") # Liquid template
|
|
75
|
+
next if link =~ %r{\A[a-z][a-z0-9+.-]*:}i # scheme: http:, mailto:, etc.
|
|
76
|
+
next if link.start_with?("#") # anchor-only
|
|
77
|
+
next if link.start_with?("/") # absolute site URL (Jekyll permalink)
|
|
78
|
+
link = link.split("#", 2).first # strip anchor fragment
|
|
79
|
+
puts link unless link.nil? || link.empty?
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
' "$filepath" 2>/dev/null || true
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
parse_args "$@"
|
|
86
|
+
|
|
87
|
+
log "Checking internal links in docs/..."
|
|
88
|
+
echo ""
|
|
89
|
+
|
|
90
|
+
# archive/ holds superseded historical docs; templates/ holds scaffolding with
|
|
91
|
+
# intentional placeholder links (link-to-config, etc.) — neither is "live" docs.
|
|
92
|
+
for filepath in $(find "$REPO_ROOT/docs" -name "*.md" \
|
|
93
|
+
-not -path "*/archive/*" -not -path "*/templates/*" 2>/dev/null | sort); do
|
|
94
|
+
relpath="${filepath#$REPO_ROOT/}"
|
|
95
|
+
filedir="$(dirname "$filepath")"
|
|
96
|
+
file_errors=0
|
|
97
|
+
|
|
98
|
+
while IFS= read -r link; do
|
|
99
|
+
[[ -z "$link" ]] && continue
|
|
100
|
+
|
|
101
|
+
# Resolve relative to the file's directory (absolute site URLs are
|
|
102
|
+
# filtered out in extract_relative_links)
|
|
103
|
+
target="$filedir/$link"
|
|
104
|
+
|
|
105
|
+
# Normalize (remove ./ and resolve ..)
|
|
106
|
+
target="$(cd "$(dirname "$target")" 2>/dev/null && pwd)/$(basename "$target")" 2>/dev/null || target="$filedir/$link"
|
|
107
|
+
|
|
108
|
+
if [[ ! -f "$target" && ! -d "$target" ]]; then
|
|
109
|
+
warn " BROKEN: $relpath → $link"
|
|
110
|
+
file_errors=$((file_errors + 1))
|
|
111
|
+
ERRORS=$((ERRORS + 1))
|
|
112
|
+
else
|
|
113
|
+
debug " OK: $relpath → $link"
|
|
114
|
+
fi
|
|
115
|
+
done < <(extract_relative_links "$filepath")
|
|
116
|
+
|
|
117
|
+
FILES_CHECKED=$((FILES_CHECKED + 1))
|
|
118
|
+
done
|
|
119
|
+
|
|
120
|
+
echo ""
|
|
121
|
+
log "Results: $FILES_CHECKED files checked, $ERRORS broken links"
|
|
122
|
+
|
|
123
|
+
[[ $ERRORS -eq 0 ]] || exit 1
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# =========================================================================
|
|
3
|
+
# scripts/docs/lint-frontmatter.sh — Front matter validator for docs/
|
|
4
|
+
# =========================================================================
|
|
5
|
+
# Checks every content .md file under docs/ (excluding READMEs and archive/)
|
|
6
|
+
# for the required front matter fields defined in the schema.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# ./scripts/docs/lint-frontmatter.sh # validate
|
|
10
|
+
# ./scripts/docs/lint-frontmatter.sh --fix # inject skeleton where missing
|
|
11
|
+
# ./scripts/docs/lint-frontmatter.sh --verbose # show per-file results
|
|
12
|
+
# =========================================================================
|
|
13
|
+
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
17
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
18
|
+
source "$SCRIPT_DIR/../lib/common.sh"
|
|
19
|
+
source "$SCRIPT_DIR/../lib/frontmatter.sh"
|
|
20
|
+
|
|
21
|
+
FIX_MODE=false
|
|
22
|
+
REQUIRED_FIELDS=(title description date lastmod categories tags author)
|
|
23
|
+
ERRORS=0
|
|
24
|
+
FILES_OK=0
|
|
25
|
+
FILES_MISSING=0
|
|
26
|
+
|
|
27
|
+
# Bash 3.2-compatible file-list builder (no mapfile)
|
|
28
|
+
list_docs_files() {
|
|
29
|
+
find "$REPO_ROOT/docs" -name "*.md" \
|
|
30
|
+
-not -name "README.md" \
|
|
31
|
+
-not -path "*/archive/*" \
|
|
32
|
+
| sort
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
parse_args() {
|
|
36
|
+
while [[ $# -gt 0 ]]; do
|
|
37
|
+
case "$1" in
|
|
38
|
+
--fix) FIX_MODE=true ;;
|
|
39
|
+
--verbose) export VERBOSE=true ;;
|
|
40
|
+
--help|-h) show_usage; exit 0 ;;
|
|
41
|
+
*) warn "Unknown option: $1" ;;
|
|
42
|
+
esac
|
|
43
|
+
shift
|
|
44
|
+
done
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
show_usage() {
|
|
48
|
+
cat << 'EOF'
|
|
49
|
+
Front Matter Linter for docs/
|
|
50
|
+
|
|
51
|
+
USAGE:
|
|
52
|
+
./scripts/docs/lint-frontmatter.sh [OPTIONS]
|
|
53
|
+
|
|
54
|
+
REQUIRED FIELDS (per .github/instructions/documentation.instructions.md):
|
|
55
|
+
title, description, date, lastmod, categories, tags, author
|
|
56
|
+
|
|
57
|
+
EXCLUDES:
|
|
58
|
+
- README.md files (directory indexes)
|
|
59
|
+
- docs/archive/** (historical docs)
|
|
60
|
+
EOF
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# Determine tags from directory name
|
|
64
|
+
dir_tags() {
|
|
65
|
+
local dir="$1"
|
|
66
|
+
case "$dir" in
|
|
67
|
+
ui) echo "[ui, styling, theme]" ;;
|
|
68
|
+
architecture) echo "[architecture, design]" ;;
|
|
69
|
+
development) echo "[development, contributing]" ;;
|
|
70
|
+
systems) echo "[systems, automation]" ;;
|
|
71
|
+
installation) echo "[installation, setup]" ;;
|
|
72
|
+
features) echo "[features]" ;;
|
|
73
|
+
implementation) echo "[implementation, changelog]" ;;
|
|
74
|
+
releases) echo "[releases]" ;;
|
|
75
|
+
templates) echo "[templates]" ;;
|
|
76
|
+
*) echo "[docs]" ;;
|
|
77
|
+
esac
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Inject skeleton front matter at the top of a file
|
|
81
|
+
inject_frontmatter() {
|
|
82
|
+
local filepath="$1"
|
|
83
|
+
local relpath="${filepath#$REPO_ROOT/}"
|
|
84
|
+
local subdir
|
|
85
|
+
subdir="$(echo "$relpath" | cut -d'/' -f2)"
|
|
86
|
+
|
|
87
|
+
# Extract title from first H1 heading
|
|
88
|
+
local title
|
|
89
|
+
title="$(grep -m1 "^# " "$filepath" 2>/dev/null | sed 's/^# //' || true)"
|
|
90
|
+
[[ -z "$title" ]] && title="$(basename "$filepath" .md | tr '-' ' ' | sed 's/\b./\u&/g')"
|
|
91
|
+
|
|
92
|
+
# Dates from git history
|
|
93
|
+
local date lastmod now
|
|
94
|
+
now="$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")"
|
|
95
|
+
date="$(git -C "$REPO_ROOT" log --diff-filter=A --follow --format="%aI" -- "$relpath" 2>/dev/null | tail -1 || true)"
|
|
96
|
+
lastmod="$(git -C "$REPO_ROOT" log -1 --format="%aI" -- "$relpath" 2>/dev/null || true)"
|
|
97
|
+
[[ -z "$date" ]] && date="$now"
|
|
98
|
+
[[ -z "$lastmod" ]] && lastmod="$now"
|
|
99
|
+
|
|
100
|
+
# Normalize to ISO 8601 with milliseconds
|
|
101
|
+
date="$(echo "$date" | ruby -rtime -e 'puts Time.parse(STDIN.read.strip).utc.strftime("%Y-%m-%dT%H:%M:%S.000Z")' 2>/dev/null || echo "$now")"
|
|
102
|
+
lastmod="$(echo "$lastmod" | ruby -rtime -e 'puts Time.parse(STDIN.read.strip).utc.strftime("%Y-%m-%dT%H:%M:%S.000Z")' 2>/dev/null || echo "$now")"
|
|
103
|
+
|
|
104
|
+
local tags
|
|
105
|
+
tags="$(dir_tags "$subdir")"
|
|
106
|
+
|
|
107
|
+
local skeleton
|
|
108
|
+
skeleton="---
|
|
109
|
+
title: \"${title}\"
|
|
110
|
+
description: \"TODO: Add a 120-160 character description of this document.\"
|
|
111
|
+
date: ${date}
|
|
112
|
+
lastmod: ${lastmod}
|
|
113
|
+
categories: [docs]
|
|
114
|
+
tags: ${tags}
|
|
115
|
+
author: bamr87
|
|
116
|
+
---"
|
|
117
|
+
|
|
118
|
+
# Prepend skeleton to file
|
|
119
|
+
local tmpfile
|
|
120
|
+
tmpfile="$(mktemp)"
|
|
121
|
+
printf '%s\n\n' "$skeleton" | cat - "$filepath" > "$tmpfile"
|
|
122
|
+
mv "$tmpfile" "$filepath"
|
|
123
|
+
info " Injected front matter: $relpath"
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
parse_args "$@"
|
|
127
|
+
|
|
128
|
+
total_files=0
|
|
129
|
+
for f in $(list_docs_files); do total_files=$((total_files + 1)); done
|
|
130
|
+
log "Checking $total_files docs files for required front matter..."
|
|
131
|
+
echo ""
|
|
132
|
+
|
|
133
|
+
for filepath in $(list_docs_files); do
|
|
134
|
+
relpath="${filepath#$REPO_ROOT/}"
|
|
135
|
+
missing=()
|
|
136
|
+
|
|
137
|
+
# Check if front matter exists at all
|
|
138
|
+
local_first_line=""
|
|
139
|
+
IFS= read -r local_first_line < "$filepath" || true
|
|
140
|
+
|
|
141
|
+
if [[ ! "$local_first_line" =~ ^--- ]]; then
|
|
142
|
+
if [[ "$FIX_MODE" == "true" ]]; then
|
|
143
|
+
inject_frontmatter "$filepath"
|
|
144
|
+
FILES_OK=$((FILES_OK + 1))
|
|
145
|
+
continue
|
|
146
|
+
else
|
|
147
|
+
warn " MISSING front matter: $relpath"
|
|
148
|
+
FILES_MISSING=$((FILES_MISSING + 1))
|
|
149
|
+
ERRORS=$((ERRORS + 1))
|
|
150
|
+
continue
|
|
151
|
+
fi
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
# Check each required field
|
|
155
|
+
fm="$(extract_frontmatter "$filepath" 2>/dev/null || true)"
|
|
156
|
+
missing_list=""
|
|
157
|
+
for field in "${REQUIRED_FIELDS[@]}"; do
|
|
158
|
+
if ! echo "$fm" | grep -q "^${field}:"; then
|
|
159
|
+
missing_list="$missing_list $field"
|
|
160
|
+
fi
|
|
161
|
+
done
|
|
162
|
+
|
|
163
|
+
# Description quality: presence alone is not enough. Reject the --fix
|
|
164
|
+
# skeleton placeholder and obvious stubs so a "compliant" run can't ship
|
|
165
|
+
# docs with a TODO description. Schema target is a 120-160 char sentence.
|
|
166
|
+
desc_problem=""
|
|
167
|
+
if echo "$fm" | grep -q "^description:"; then
|
|
168
|
+
desc_value="$(echo "$fm" | grep -m1 "^description:" | sed -E 's/^description:[[:space:]]*//; s/^"(.*)"$/\1/; s/^'"'"'(.*)'"'"'$/\1/')"
|
|
169
|
+
if echo "$desc_value" | grep -qi "TODO"; then
|
|
170
|
+
desc_problem="placeholder TODO description"
|
|
171
|
+
elif [[ "${#desc_value}" -lt 40 ]]; then
|
|
172
|
+
desc_problem="description too short (${#desc_value} chars; aim for 120-160)"
|
|
173
|
+
fi
|
|
174
|
+
fi
|
|
175
|
+
|
|
176
|
+
if [[ -n "$missing_list" || -n "$desc_problem" ]]; then
|
|
177
|
+
local_msg=" INCOMPLETE ($relpath):"
|
|
178
|
+
[[ -n "$missing_list" ]] && local_msg="$local_msg missing:$missing_list"
|
|
179
|
+
[[ -n "$desc_problem" ]] && local_msg="$local_msg $desc_problem"
|
|
180
|
+
warn "$local_msg"
|
|
181
|
+
ERRORS=$((ERRORS + 1))
|
|
182
|
+
else
|
|
183
|
+
debug " OK: $relpath"
|
|
184
|
+
FILES_OK=$((FILES_OK + 1))
|
|
185
|
+
fi
|
|
186
|
+
done
|
|
187
|
+
|
|
188
|
+
incomplete=$((ERRORS - FILES_MISSING))
|
|
189
|
+
echo ""
|
|
190
|
+
log "Results: ${FILES_OK} OK, ${FILES_MISSING} missing front matter, ${incomplete} incomplete"
|
|
191
|
+
|
|
192
|
+
if [[ $ERRORS -gt 0 ]]; then
|
|
193
|
+
if [[ "$FIX_MODE" == "false" ]]; then
|
|
194
|
+
info "Run with --fix to inject skeleton front matter into missing files."
|
|
195
|
+
fi
|
|
196
|
+
exit 1
|
|
197
|
+
fi
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# =========================================================================
|
|
3
|
+
# scripts/docs/validate.sh — Docs validation orchestrator
|
|
4
|
+
# =========================================================================
|
|
5
|
+
# Runs all docs health checks: front matter, links, and freshness.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# ./scripts/docs/validate.sh # run all checks
|
|
9
|
+
# ./scripts/docs/validate.sh --lint # front matter only
|
|
10
|
+
# ./scripts/docs/validate.sh --links # links only
|
|
11
|
+
# ./scripts/docs/validate.sh --freshness # freshness only
|
|
12
|
+
# ./scripts/docs/validate.sh --fix # inject missing front matter
|
|
13
|
+
# ./scripts/docs/validate.sh --verbose # detailed output
|
|
14
|
+
# =========================================================================
|
|
15
|
+
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
19
|
+
source "$SCRIPT_DIR/../lib/common.sh"
|
|
20
|
+
|
|
21
|
+
RUN_LINT=true
|
|
22
|
+
RUN_LINKS=true
|
|
23
|
+
RUN_FRESHNESS=true
|
|
24
|
+
FIX_MODE=false
|
|
25
|
+
OVERALL_STATUS=0
|
|
26
|
+
|
|
27
|
+
parse_args() {
|
|
28
|
+
while [[ $# -gt 0 ]]; do
|
|
29
|
+
case "$1" in
|
|
30
|
+
--lint) RUN_LINKS=false; RUN_FRESHNESS=false ;;
|
|
31
|
+
--links) RUN_LINT=false; RUN_FRESHNESS=false ;;
|
|
32
|
+
--freshness) RUN_LINT=false; RUN_LINKS=false ;;
|
|
33
|
+
--fix) FIX_MODE=true ;;
|
|
34
|
+
--verbose) export VERBOSE=true ;;
|
|
35
|
+
--help|-h) show_usage; exit 0 ;;
|
|
36
|
+
*) warn "Unknown option: $1" ;;
|
|
37
|
+
esac
|
|
38
|
+
shift
|
|
39
|
+
done
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
show_usage() {
|
|
43
|
+
cat << 'EOF'
|
|
44
|
+
Docs Validation Suite for zer0-mistakes
|
|
45
|
+
|
|
46
|
+
USAGE:
|
|
47
|
+
./scripts/docs/validate.sh [OPTIONS]
|
|
48
|
+
|
|
49
|
+
OPTIONS:
|
|
50
|
+
--lint Front matter compliance only
|
|
51
|
+
--links Link checking only
|
|
52
|
+
--freshness Staleness check only
|
|
53
|
+
--fix Inject skeleton front matter into files that lack it
|
|
54
|
+
--verbose Detailed output
|
|
55
|
+
--help Show this message
|
|
56
|
+
|
|
57
|
+
CHECKS:
|
|
58
|
+
lint Every docs/**/*.md (non-README, non-archive) has required
|
|
59
|
+
front matter: title, description, date, lastmod, categories,
|
|
60
|
+
tags, author
|
|
61
|
+
links Internal markdown links resolve to existing files
|
|
62
|
+
freshness Files where lastmod trails the last git commit by > 60 days
|
|
63
|
+
EOF
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
run_check() {
|
|
67
|
+
local name="$1"; local script="$2"; shift 2
|
|
68
|
+
log "Running: $name"
|
|
69
|
+
if "$script" "$@"; then
|
|
70
|
+
log " PASS: $name"
|
|
71
|
+
else
|
|
72
|
+
warn " FAIL: $name"
|
|
73
|
+
OVERALL_STATUS=1
|
|
74
|
+
fi
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
parse_args "$@"
|
|
78
|
+
|
|
79
|
+
[[ "$FIX_MODE" == "true" ]] && EXTRA="--fix" || EXTRA=""
|
|
80
|
+
|
|
81
|
+
[[ "$RUN_LINT" == "true" ]] && run_check "Front matter lint" "$SCRIPT_DIR/lint-frontmatter.sh" $EXTRA
|
|
82
|
+
[[ "$RUN_LINKS" == "true" ]] && run_check "Link check" "$SCRIPT_DIR/check-links.sh"
|
|
83
|
+
[[ "$RUN_FRESHNESS" == "true" ]] && run_check "Freshness check" "$SCRIPT_DIR/check-freshness.sh"
|
|
84
|
+
|
|
85
|
+
if [[ $OVERALL_STATUS -eq 0 ]]; then
|
|
86
|
+
log "All docs checks passed."
|
|
87
|
+
else
|
|
88
|
+
error "One or more docs checks failed."
|
|
89
|
+
exit 1
|
|
90
|
+
fi
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jekyll-theme-zer0
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.11.
|
|
4
|
+
version: 1.11.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Amr Abdel
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-06-
|
|
11
|
+
date: 2026-06-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: jekyll
|
|
@@ -368,6 +368,10 @@ files:
|
|
|
368
368
|
- scripts/build
|
|
369
369
|
- scripts/convert-notebooks.sh
|
|
370
370
|
- scripts/docker-publish
|
|
371
|
+
- scripts/docs/check-freshness.sh
|
|
372
|
+
- scripts/docs/check-links.sh
|
|
373
|
+
- scripts/docs/lint-frontmatter.sh
|
|
374
|
+
- scripts/docs/validate.sh
|
|
371
375
|
- scripts/example-usage.sh
|
|
372
376
|
- scripts/features/generate-preview-images
|
|
373
377
|
- scripts/features/install-preview-generator
|