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
50 changes: 50 additions & 0 deletions .github/workflows/vale-autofix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,16 @@ jobs:
echo "committed=true" >> "$GITHUB_OUTPUT"
fi

- name: Update heading anchors (Phase 1)
if: steps.phase1-commit.outputs.committed == 'true'
run: |
RESULT=$(bash scripts/vale-autofix.sh --anchors-only 2>&1 || true)
echo "$RESULT"
if ! git diff --quiet; then
git add -A docs/
git commit -m "fix(vale): update anchor links for changed headings"
fi

- name: Re-run Vale for remaining violations
id: vale-remaining
if: steps.vale-initial.outputs.total > 0
Expand Down Expand Up @@ -171,6 +181,26 @@ jobs:
2. Apply a fix that resolves the Vale rule while preserving the author's meaning
3. If you are NOT confident in a fix (ambiguous context, multiple valid interpretations, fix would change meaning) SKIP it

HEADING ANCHOR UPDATES:
When you modify a heading line (any line starting with #), you MUST update all anchor links that reference the old heading.

1. Before editing a heading, record the original heading text
2. Compute the old anchor slug:
- If the heading has a {#custom-id} suffix, use custom-id as the slug
- Otherwise: strip the # prefix and whitespace, lowercase, remove everything except [a-z0-9 -], replace spaces with hyphens, collapse consecutive hyphens, trim leading/trailing hyphens
- Examples: "## Do Not Click" → do-not-click, "## Step 1: Install" → step-1-install, "## Setup {#setup}" → setup
3. After editing, compute the new anchor slug the same way
4. If the slug changed, determine the product/version folder from the file path:
- Multi-version: docs/<product>/<version>/ (e.g., docs/accessanalyzer/12.0/)
- Single-version: docs/<product>/ (e.g., docs/threatprevention/)
- The folder is the first 2 or 3 segments of the path after docs/. If the second segment is a version number (digits/dots), include it.
5. Search ALL .md files in that folder (not just PR-changed files) for link patterns containing #old-slug:
- ](#old-slug) — same-page links
- ](filename#old-slug) — relative links
- ](path/to/filename#old-slug) — deeper relative links
6. Replace #old-slug with #new-slug in every match
7. Include each anchor update in the fixed array of your summary JSON, using the same check value as the heading fix that caused it, with action like "updated anchor link from #old-slug to #new-slug"

After fixing, write a JSON summary to /tmp/phase2-summary.json with this structure:
```json
{
Expand Down Expand Up @@ -218,6 +248,26 @@ jobs:

Step 4: Fix each violation in-place, preserving the author's meaning. If you are NOT confident in a fix (ambiguous context, multiple valid interpretations, fix would change meaning), SKIP it.

HEADING ANCHOR UPDATES:
When you modify a heading line (any line starting with #), you MUST update all anchor links that reference the old heading.

1. Before editing a heading, record the original heading text
2. Compute the old anchor slug:
- If the heading has a {#custom-id} suffix, use custom-id as the slug
- Otherwise: strip the # prefix and whitespace, lowercase, remove everything except [a-z0-9 -], replace spaces with hyphens, collapse consecutive hyphens, trim leading/trailing hyphens
- Examples: "## Do Not Click" → do-not-click, "## Step 1: Install" → step-1-install, "## Setup {#setup}" → setup
3. After editing, compute the new anchor slug the same way
4. If the slug changed, determine the product/version folder from the file path:
- Multi-version: docs/<product>/<version>/ (e.g., docs/accessanalyzer/12.0/)
- Single-version: docs/<product>/ (e.g., docs/threatprevention/)
- The folder is the first 2 or 3 segments of the path after docs/. If the second segment is a version number (digits/dots), include it.
5. Search ALL .md files in that folder (not just PR-changed files) for link patterns containing #old-slug:
- ](#old-slug) — same-page links
- ](filename#old-slug) — relative links
- ](path/to/filename#old-slug) — deeper relative links
6. Replace #old-slug with #new-slug in every match
7. Include each anchor update in the fixed array of your summary JSON, using the same rule value as the heading fix that caused it, with action like "updated anchor link from #old-slug to #new-slug"

Step 5: Write a JSON summary to /tmp/dale-summary.json with this structure:
```json
{
Expand Down
13 changes: 0 additions & 13 deletions .vale/styles/Netwrix/HeadingPunctuation.yml

This file was deleted.

107 changes: 107 additions & 0 deletions scripts/test-anchor-update.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/usr/bin/env bash
# test-anchor-update.sh — integration test for update_heading_anchors()
# Creates a temp git repo, makes a heading change, and verifies anchor links update
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT

echo "Setting up test repo in $TMPDIR..."

cd "$TMPDIR"
git init -q
git config user.name "test"
git config user.email "test@test.com"

# Create a product/version folder structure
mkdir -p docs/testproduct/1.0/install
mkdir -p docs/testproduct/1.0/admin

# File with a heading that will change
cat > docs/testproduct/1.0/install/setup.md << 'MDEOF'
# Install the Product

## Do Not Use the Old Method

Follow these steps instead. See also [configure settings](#configure-settings).

## Configure Settings

See the configuration guide. Do not use [the old method](#do-not-use-the-old-method).
MDEOF

# File with links to the heading above
cat > docs/testproduct/1.0/admin/guide.md << 'MDEOF'
# Admin Guide

See [old method](../install/setup.md#do-not-use-the-old-method) for details.

Also check [configure](../install/setup.md#configure-settings).
MDEOF

# Same-page link in the same file
cat > docs/testproduct/1.0/install/overview.md << 'MDEOF'
# Overview

For setup, see [setup instructions](setup.md#do-not-use-the-old-method).
MDEOF

git add -A
git commit -q -m "initial"

# Now simulate Phase 1 changing the heading (contractions fix)
sed -i "s/## Do Not Use the Old Method/## Don't Use the Old Method/" docs/testproduct/1.0/install/setup.md

git add -A
git commit -q -m "fix(vale): auto-fix substitutions and removals"

# Run the anchor update function
source "$SCRIPT_DIR/vale-autofix.sh" --test
update_heading_anchors

# Verify: guide.md should have updated anchor
PASS=0
FAIL=0

check_contains() {
local file="$1"
local expected="$2"
local label="$3"
if grep -qF "$expected" "$file"; then
PASS=$((PASS + 1))
else
FAIL=$((FAIL + 1))
echo "FAIL: $label"
echo " expected '$expected' in $file"
echo " actual content:"
cat "$file"
fi
}

check_not_contains() {
local file="$1"
local unexpected="$2"
local label="$3"
if grep -qF "$unexpected" "$file"; then
FAIL=$((FAIL + 1))
echo "FAIL: $label"
echo " did not expect '$unexpected' in $file"
else
PASS=$((PASS + 1))
fi
}

check_contains "docs/testproduct/1.0/admin/guide.md" "#dont-use-the-old-method" "cross-file link updated"
check_not_contains "docs/testproduct/1.0/admin/guide.md" "#do-not-use-the-old-method" "old cross-file link removed"
check_contains "docs/testproduct/1.0/install/overview.md" "#dont-use-the-old-method" "relative link updated"
check_not_contains "docs/testproduct/1.0/install/overview.md" "#do-not-use-the-old-method" "old relative link removed"
check_contains "docs/testproduct/1.0/admin/guide.md" "#configure-settings" "unrelated link unchanged"
check_contains "docs/testproduct/1.0/install/setup.md" "#dont-use-the-old-method" "same-file anchor link updated"
check_not_contains "docs/testproduct/1.0/install/setup.md" "#do-not-use-the-old-method" "old same-file anchor link removed"

echo ""
echo "Results: $PASS passed, $FAIL failed"
if [ "$FAIL" -gt 0 ]; then
exit 1
fi
57 changes: 57 additions & 0 deletions scripts/test-slugify.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
# test-slugify.sh — unit tests for the slugify function in vale-autofix.sh
set -euo pipefail

# Source just the functions (--test mode skips the main script logic)
source "$(dirname "$0")/vale-autofix.sh" --test

PASS=0
FAIL=0

assert_slug() {
local input="$1"
local expected="$2"
local actual
actual=$(slugify "$input")
if [ "$actual" = "$expected" ]; then
PASS=$((PASS + 1))
else
FAIL=$((FAIL + 1))
echo "FAIL: slugify '$input'"
echo " expected: '$expected'"
echo " actual: '$actual'"
fi
}

# Basic headings
assert_slug "## Hello World" "hello-world"
assert_slug "### Step 1: Install the Agent" "step-1-install-the-agent"
assert_slug "# Overview" "overview"

# Contractions (apostrophes stripped)
assert_slug "## Don't Click Here" "dont-click-here"
assert_slug "## Can't Stop Won't Stop" "cant-stop-wont-stop"

# Punctuation stripped
assert_slug "## What is This?" "what-is-this"
assert_slug "## Install (Optional)" "install-optional"
assert_slug "## Step 1. Configure" "step-1-configure"

# Custom anchor IDs
assert_slug '## Setup the Application {#setup}' "setup"
assert_slug '### Advanced Options {#advanced-opts}' "advanced-opts"

# Extra whitespace and hyphens
assert_slug "## Lots of Spaces" "lots-of-spaces"
assert_slug "## Already-Hyphenated--Word" "already-hyphenated-word"

# Edge cases
assert_slug "## 123 Numbers First" "123-numbers-first"
assert_slug "## ALL CAPS HEADING" "all-caps-heading"
assert_slug '## Quotes "and" Stuff' "quotes-and-stuff"

echo ""
echo "Results: $PASS passed, $FAIL failed"
if [ "$FAIL" -gt 0 ]; then
exit 1
fi
Loading
Loading