From af9c48878cd39f9df1b22fc429f489d38573fe24 Mon Sep 17 00:00:00 2001 From: jth-nw Date: Thu, 12 Mar 2026 15:55:36 -0500 Subject: [PATCH 1/5] feat: add Vale pre-push hook to block pushes with linting errors Husky pre-push hook runs Vale on changed docs/*.md files and blocks the push if errors are found or if Vale is not installed. Updated README with Vale installation instructions and local usage guidance. Co-Authored-By: Claude Opus 4.6 --- .husky/pre-push | 80 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 47 +++++++++++++++++++++++++++-- package.json | 3 +- 3 files changed, 127 insertions(+), 3 deletions(-) create mode 100755 .husky/pre-push diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 0000000000..46fe52a6ee --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,80 @@ +#!/bin/sh + +# Vale pre-push hook +# Runs Vale on changed docs/*.md files before allowing a push. +# Blocks the push if Vale is not installed or if errors are found. + +# Check if Vale is installed +if ! command -v vale >/dev/null 2>&1; then + echo "" + echo "ERROR: Vale is not installed." + echo "" + echo "Vale is required to push documentation changes. Install it using one of these methods:" + echo "" + echo " macOS: brew install vale" + echo " Linux: sudo snap install vale" + echo " Windows: choco install vale" + echo " Manual: Download from https://github.com/errata-ai/vale/releases" + echo "" + echo "After installing, run 'vale --version' to verify, then try your push again." + echo "" + exit 1 +fi + +# Pre-push hook receives: as args +# and reads lines of: from stdin +ZERO_SHA="0000000000000000000000000000000000000000" + +CHANGED_MD_FILES="" +while read -r LOCAL_REF LOCAL_SHA REMOTE_REF REMOTE_SHA; do + if [ "$LOCAL_SHA" = "$ZERO_SHA" ]; then + # Deleting a branch, nothing to check + continue + fi + + if [ "$REMOTE_SHA" = "$ZERO_SHA" ]; then + # New branch — compare against the merge base with dev + RANGE="$(git merge-base dev "$LOCAL_SHA" 2>/dev/null || echo "")..${LOCAL_SHA}" + if [ "$RANGE" = "..${LOCAL_SHA}" ]; then + # Fallback: check all files in the push + RANGE="$LOCAL_SHA" + fi + else + RANGE="${REMOTE_SHA}..${LOCAL_SHA}" + fi + + FILES=$(git diff --name-only --diff-filter=ACMR "$RANGE" 2>/dev/null | grep -E '^docs/.*\.md$' | grep -v '/CLAUDE\.md$' | grep -v '/SKILL\.md$' || true) + if [ -n "$FILES" ]; then + CHANGED_MD_FILES=$(printf "%s\n%s" "$CHANGED_MD_FILES" "$FILES" | sort -u | sed '/^$/d') + fi +done + +if [ -z "$CHANGED_MD_FILES" ]; then + exit 0 +fi + +echo "Running Vale on changed documentation files..." +echo "" + +VALE_FAILED=0 + +while IFS= read -r FILE; do + if [ -f "$FILE" ]; then + OUTPUT=$(vale "$FILE" 2>&1) + EXIT_CODE=$? + if [ $EXIT_CODE -ne 0 ]; then + echo "$OUTPUT" + echo "" + VALE_FAILED=1 + fi + fi +done <<< "$CHANGED_MD_FILES" + +if [ "$VALE_FAILED" -eq 1 ]; then + echo "Push blocked: Vale found errors in the files above." + echo "Fix the issues and try again." + echo "" + exit 1 +fi + +echo "Vale passed on all changed documentation files." diff --git a/README.md b/README.md index bbd4e4443b..39099e0499 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,38 @@ This documentation site serves all Netwrix product documentation. ### Prerequisites -- **Node.js 18+** +- **Node.js 22+** - **npm** - **Git** +- **Vale** (style linter — required for pushing documentation changes) + +### Install Vale + +Vale is required to push changes to documentation files. The pre-push hook runs Vale automatically and blocks pushes that have linting errors. + +**macOS:** +```bash +brew install vale +``` + +**Linux:** +```bash +sudo snap install vale +``` + +**Windows:** +```bash +choco install vale +``` + +**Manual install (any platform):** + +Download the latest release from [github.com/errata-ai/vale/releases](https://github.com/errata-ai/vale/releases), extract the binary, and add it to your PATH. + +Verify the installation: +```bash +vale --version +``` ### Installation @@ -36,13 +65,27 @@ This documentation site serves all Netwrix product documentation. git clone https://github.com/netwrix/docs.git cd docs -# Install dependencies +# Install dependencies (also sets up the pre-push hook via Husky) npm install # Start development server npm run start ``` +### Run Vale Locally + +Run Vale on a file before pushing to catch issues early: + +```bash +vale docs/path/to/file.md +``` + +Run Vale on all changed docs files: + +```bash +git diff --name-only dev | grep '^docs/.*\.md$' | xargs vale +``` + ## 📁 Project Structure ``` diff --git a/package.json b/package.json index ee2ec0e8a0..10c7daf9cd 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "kb:clean": "node scripts/copy-kb-to-versions.mjs --clean", "kb:dry": "node scripts/copy-kb-to-versions.mjs --dry", "kb:test:aa": "npm run kb:clean && npm run prestart && npm run start", - "kb:prodtest:aa": "npm run kb:clean && npm run prebuild && npm run build && npm run serve" + "kb:prodtest:aa": "npm run kb:clean && npm run prebuild && npm run build && npm run serve", + "prepare": "husky" }, "dependencies": { "@docusaurus/babel": "^3.9.2", From 5eb6c9efee870ff902e7ccfb0b79bcd742783baa Mon Sep 17 00:00:00 2001 From: jth-nw Date: Thu, 12 Mar 2026 16:01:09 -0500 Subject: [PATCH 2/5] refactor: remove Vale from CI pipeline, enforce pre-push only Vale is now handled by the Husky pre-push hook. Remove Vale install, Vale inline comments, and Vale re-run steps from the workflow. Update editorial review prompt, footer, and doc-pr-fix skill to remove Vale references. Dale remains as inline comments in CI. Co-Authored-By: Claude Opus 4.6 --- .claude/skills/doc-pr-fix/SKILL.md | 14 +-- .github/workflows/claude-doc-pr.yml | 157 +++------------------------- 2 files changed, 15 insertions(+), 156 deletions(-) diff --git a/.claude/skills/doc-pr-fix/SKILL.md b/.claude/skills/doc-pr-fix/SKILL.md index ff86184556..a05d7ca718 100644 --- a/.claude/skills/doc-pr-fix/SKILL.md +++ b/.claude/skills/doc-pr-fix/SKILL.md @@ -1,6 +1,6 @@ --- name: doc-pr-fix -description: "Autonomous fixer for documentation PRs. Triggered by @claude comments on PRs targeting dev. Reads the writer's request and the existing doc-pr review, then applies fixes, runs Vale and Dale until clean, and commits. Use this skill whenever a writer tags @claude on a documentation PR — not for interactive help (use doc-help for that), but for autonomous, single-shot fixes in CI." +description: "Autonomous fixer for documentation PRs. Triggered by @claude comments on PRs targeting dev. Reads the writer's request and the existing doc-pr review, then applies fixes and commits. Vale is enforced pre-push so only Dale and editorial issues appear in PR reviews. Use this skill whenever a writer tags @claude on a documentation PR — not for interactive help (use doc-help for that), but for autonomous, single-shot fixes in CI." argument-hint: "[pr-number] [writer-comment]" --- @@ -41,7 +41,7 @@ Parse the writer's comment to determine what they want. Common patterns: Work through the requested fixes methodically: -- For **linting fixes** (Vale/Dale): fix each flagged issue in order, file by file +- For **linting fixes** (Dale): fix each flagged issue in order, file by file - For **editorial fixes**: apply the suggested changes from the review, or if the writer asked for something broader ("improve the flow"), read the full document and apply edits that address the request while following Netwrix style - For **explanations**: post a PR comment explaining the issue and how to fix it, then stop — don't edit files @@ -52,15 +52,7 @@ When editing: ## Step 4: Verify -After all edits, run Vale on every file you changed: - -```bash -vale -``` - -Fix any new Vale errors. Re-run until zero errors remain. - -Do NOT run Dale or any other skills during verification — just Vale. +Review your edits to ensure they don't introduce new issues. Do NOT run Dale or any other skills during verification. ## Step 5: Commit and push diff --git a/.github/workflows/claude-doc-pr.yml b/.github/workflows/claude-doc-pr.yml index c95ce1db3b..50d6899f8a 100644 --- a/.github/workflows/claude-doc-pr.yml +++ b/.github/workflows/claude-doc-pr.yml @@ -68,73 +68,6 @@ jobs: -f message="Superseded by new review" -f event="DISMISS" 2>/dev/null || true done - - name: Install Vale - if: steps.changed-files.outputs.count > 0 - run: | - VERSION=$(curl -s "https://api.github.com/repos/errata-ai/vale/releases/latest" | jq -r '.tag_name') - curl -sfL "https://github.com/errata-ai/vale/releases/download/${VERSION}/vale_${VERSION#v}_Linux_64-bit.tar.gz" \ - | sudo tar -xz -C /usr/local/bin vale - - - name: Run Vale and post inline comments - id: vale - if: steps.changed-files.outputs.count > 0 - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - IFS=',' read -ra FILES <<< "${{ steps.changed-files.outputs.files }}" - VALE_COUNT=0 - COMMENTS_JSON="[]" - - for FILE in "${FILES[@]}"; do - if [ -f "$FILE" ]; then - RESULT=$(vale --output=line "$FILE" 2>&1 || true) - if [ -n "$RESULT" ]; then - while IFS= read -r LINE; do - # Format: file:line:col:rule:message - LINE_NUM=$(echo "$LINE" | cut -d: -f2) - RULE=$(echo "$LINE" | cut -d: -f4) - MESSAGE=$(echo "$LINE" | cut -d: -f5-) - if [ -n "$LINE_NUM" ] && [ -n "$MESSAGE" ]; then - BODY="**Vale** (\`${RULE}\`): ${MESSAGE}" - COMMENTS_JSON=$(echo "$COMMENTS_JSON" | jq \ - --arg path "$FILE" \ - --argjson line "$LINE_NUM" \ - --arg body "$BODY" \ - '. += [{"path": $path, "line": $line, "body": $body}]') - VALE_COUNT=$((VALE_COUNT + 1)) - fi - done <<< "$RESULT" - fi - fi - done - - echo "vale_count=$VALE_COUNT" >> "$GITHUB_OUTPUT" - - if [ "$VALE_COUNT" -gt 0 ]; then - MAX_INLINE=25 - INLINE_COMMENTS=$(echo "$COMMENTS_JSON" | jq ".[:$MAX_INLINE]") - OVERFLOW=$((VALE_COUNT - MAX_INLINE)) - if [ "$OVERFLOW" -lt 0 ]; then OVERFLOW=0; fi - - if [ "$OVERFLOW" -gt 0 ]; then - OVERFLOW_MD=$(echo "$COMMENTS_JSON" | jq -r ".[$MAX_INLINE:][] | \"- **\(.path)** line \(.line): \(.body)\"") - REVIEW_BODY=$(printf "**Vale found %d issue(s).** First %d shown as inline comments.\n\n
\n%d more issue(s)\n\n%s\n\n
" \ - "$VALE_COUNT" "$MAX_INLINE" "$OVERFLOW" "$OVERFLOW_MD") - else - REVIEW_BODY="**Vale found ${VALE_COUNT} issue(s).** See inline comments below." - fi - - echo "Posting $VALE_COUNT Vale issues ($MAX_INLINE inline, $OVERFLOW overflow)" - jq -n \ - --arg body "$REVIEW_BODY" \ - --argjson comments "$INLINE_COMMENTS" \ - '{"body": $body, "event": "COMMENT", "comments": $comments}' \ - | gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews \ - --input - 2>&1 - else - echo "No Vale issues found" - fi - - name: Run Dale linting id: dale if: steps.changed-files.outputs.count > 0 @@ -231,10 +164,11 @@ jobs: - Repository: ${{ github.repository }} - PR number: ${{ github.event.pull_request.number }} - Changed files: ${{ steps.changed-files.outputs.files }} - - Vale issues: ${{ steps.vale.outputs.vale_count }} (already posted as inline comments) - Dale issues: ${{ steps.dale-post.outputs.dale_count }} (already posted as inline comments) - PR diff is at: /tmp/pr-diff.txt + NOTE: Vale linting is enforced by a pre-push hook. Writers resolve Vale issues locally before pushing. Do not flag or mention Vale issues in the review. + INSTRUCTIONS: Step 1: Read /tmp/pr-diff.txt to see what changed. Then read each changed file to understand context. @@ -243,7 +177,7 @@ jobs: - Voice: passive voice, first person, impersonal phrases - Clarity: hard-to-parse sentences, ambiguous references - Surface: wordiness, redundancy - Do NOT duplicate issues already caught by Vale or Dale — focus on what linters miss. + Do NOT duplicate issues already caught by Dale — focus on what linters miss. Step 3: Write the review to /tmp/doc-pr-review.md with this EXACT structure: @@ -254,13 +188,12 @@ jobs: (if no issues found, write "No editorial issues found.") ### Summary - N Vale issues, N Dale issues (see inline comments), N editorial suggestions across N files. + N Dale issues (see inline comments), N editorial suggestions across N files. --- **What to do next:** Comment `@claude` on this PR followed by your instructions to get help: - - `@claude fix all issues` — fix all Vale, Dale, and editorial issues - - `@claude fix only the Vale issues` — fix just the linting problems + - `@claude fix all issues` — fix all Dale and editorial issues - `@claude help improve the flow of this document` — get writing assistance > Automated fixes are only available for branches in this repository, not forks. @@ -329,13 +262,6 @@ jobs: ref: ${{ steps.pr-info.outputs.branch }} fetch-depth: 0 - - name: Install Vale - if: steps.pr-info.outputs.is_fork == 'false' && steps.pr-info.outputs.targets_dev == 'true' - run: | - VERSION=$(curl -s "https://api.github.com/repos/errata-ai/vale/releases/latest" | jq -r '.tag_name') - curl -sfL "https://github.com/errata-ai/vale/releases/download/${VERSION}/vale_${VERSION#v}_Linux_64-bit.tar.gz" \ - | sudo tar -xz -C /usr/local/bin vale - - name: Handle @claude request if: steps.pr-info.outputs.is_fork == 'false' && steps.pr-info.outputs.targets_dev == 'true' uses: anthropics/claude-code-action@v1 @@ -348,19 +274,19 @@ jobs: show_full_output: true prompt: | /doc-pr-fix ${{ steps.pr-info.outputs.number }} $COMMENT_BODY - claude_args: '--allowedTools "Bash(vale:*),Bash(gh:*),Bash(git:*),Read,Write,Edit,Glob,Grep,Skill(doc-pr-fix),Skill(dale)"' + claude_args: '--allowedTools "Bash(gh:*),Bash(git:*),Read,Write,Edit,Glob,Grep,Skill(doc-pr-fix),Skill(dale)"' - - name: Re-run Vale and update inline comments + - name: Resolve Dale inline comments if: steps.pr-info.outputs.is_fork == 'false' && steps.pr-info.outputs.targets_dev == 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | PR_NUMBER=${{ steps.pr-info.outputs.number }} REPO=${{ github.repository }} - - # Resolve all Vale inline comment threads so they don't block merge OWNER="${REPO%%/*}" NAME="${REPO##*/}" + + # Resolve all Dale inline comment threads THREAD_IDS=$(gh api graphql -f query=' query($owner:String!,$name:String!,$pr:Int!) { repository(owner:$owner,name:$name) { @@ -371,7 +297,7 @@ jobs: } } }' -f owner="$OWNER" -f name="$NAME" -F pr="$PR_NUMBER" \ - --jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false and ((.comments.nodes[0].body | contains("**Vale**")) or (.comments.nodes[0].body | contains("**Dale**")))) | .id' 2>/dev/null || true) + --jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false and (.comments.nodes[0].body | contains("**Dale**"))) | .id' 2>/dev/null || true) for TID in $THREAD_IDS; do gh api graphql -f query=' mutation($tid:ID!) { @@ -379,69 +305,10 @@ jobs: }' -f tid="$TID" 2>/dev/null || true done - # Dismiss all previous Vale and Dale reviews + # Dismiss all previous Dale reviews REVIEW_IDS=$(gh api repos/${REPO}/pulls/${PR_NUMBER}/reviews \ - --jq '[.[] | select(.user.login == "github-actions[bot]" and ((.body | contains("Vale found")) or (.body | contains("Dale found")))) | .id] | .[]' 2>/dev/null || true) + --jq '[.[] | select(.user.login == "github-actions[bot]" and (.body | contains("Dale found"))) | .id] | .[]' 2>/dev/null || true) for ID in $REVIEW_IDS; do gh api repos/${REPO}/pulls/${PR_NUMBER}/reviews/${ID}/dismissals \ -f message="Superseded after fixes applied" -f event="DISMISS" 2>/dev/null || true done - - # Pull latest changes from Claude's push - git pull origin ${{ steps.pr-info.outputs.branch }} 2>/dev/null || true - - # Get changed files - CHANGED_MD_FILES=$(gh pr diff "$PR_NUMBER" --name-only | grep -E '^docs/.*\.md$' | grep -v '/CLAUDE\.md$' | grep -v '/SKILL\.md$' || true) - if [ -z "$CHANGED_MD_FILES" ]; then - echo "No docs markdown files to re-check" - exit 0 - fi - - # Re-run Vale and collect results - VALE_COUNT=0 - COMMENTS_JSON="[]" - while IFS= read -r FILE; do - if [ -f "$FILE" ]; then - RESULT=$(vale --output=line "$FILE" 2>&1 || true) - if [ -n "$RESULT" ]; then - while IFS= read -r LINE; do - LINE_NUM=$(echo "$LINE" | cut -d: -f2) - RULE=$(echo "$LINE" | cut -d: -f4) - MESSAGE=$(echo "$LINE" | cut -d: -f5-) - if [ -n "$LINE_NUM" ] && [ -n "$MESSAGE" ]; then - BODY="**Vale** (\`${RULE}\`): ${MESSAGE}" - COMMENTS_JSON=$(echo "$COMMENTS_JSON" | jq \ - --arg path "$FILE" \ - --argjson line "$LINE_NUM" \ - --arg body "$BODY" \ - '. += [{"path": $path, "line": $line, "body": $body}]') - VALE_COUNT=$((VALE_COUNT + 1)) - fi - done <<< "$RESULT" - fi - fi - done <<< "$CHANGED_MD_FILES" - - if [ "$VALE_COUNT" -gt 0 ]; then - MAX_INLINE=25 - INLINE_COMMENTS=$(echo "$COMMENTS_JSON" | jq ".[:$MAX_INLINE]") - OVERFLOW=$((VALE_COUNT - MAX_INLINE)) - if [ "$OVERFLOW" -lt 0 ]; then OVERFLOW=0; fi - - if [ "$OVERFLOW" -gt 0 ]; then - OVERFLOW_MD=$(echo "$COMMENTS_JSON" | jq -r ".[$MAX_INLINE:][] | \"- **\(.path)** line \(.line): \(.body)\"") - REVIEW_BODY=$(printf "**Vale found %d remaining issue(s) after fixes.** First %d shown as inline comments.\n\n
\n%d more issue(s)\n\n%s\n\n
" \ - "$VALE_COUNT" "$MAX_INLINE" "$OVERFLOW" "$OVERFLOW_MD") - else - REVIEW_BODY="**Vale found ${VALE_COUNT} remaining issue(s) after fixes.** See inline comments below." - fi - - echo "Vale still found $VALE_COUNT issue(s) after fixes ($MAX_INLINE inline, $OVERFLOW overflow)" - jq -n \ - --arg body "$REVIEW_BODY" \ - --argjson comments "$INLINE_COMMENTS" \ - '{"body": $body, "event": "COMMENT", "comments": $comments}' \ - | gh api repos/${REPO}/pulls/${PR_NUMBER}/reviews --input - 2>&1 - else - echo "All Vale issues resolved" - fi From bd6ffdfcf0e9533a9658199d20b342ee622148cb Mon Sep 17 00:00:00 2001 From: jth-nw Date: Thu, 12 Mar 2026 16:06:31 -0500 Subject: [PATCH 3/5] fix: remove all Vale references from doc-pr-fix skill Vale is now enforced via pre-push hook, so the PR review skill only needs to reference Dale and editorial issues. Co-Authored-By: Claude Opus 4.6 --- .claude/skills/doc-pr-fix/SKILL.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.claude/skills/doc-pr-fix/SKILL.md b/.claude/skills/doc-pr-fix/SKILL.md index a05d7ca718..798cefef14 100644 --- a/.claude/skills/doc-pr-fix/SKILL.md +++ b/.claude/skills/doc-pr-fix/SKILL.md @@ -1,6 +1,6 @@ --- name: doc-pr-fix -description: "Autonomous fixer for documentation PRs. Triggered by @claude comments on PRs targeting dev. Reads the writer's request and the existing doc-pr review, then applies fixes and commits. Vale is enforced pre-push so only Dale and editorial issues appear in PR reviews. Use this skill whenever a writer tags @claude on a documentation PR — not for interactive help (use doc-help for that), but for autonomous, single-shot fixes in CI." +description: "Autonomous fixer for documentation PRs. Triggered by @claude comments on PRs targeting dev. Reads the writer's request and the existing doc-pr review, then applies fixes and commits. Only Dale and editorial issues appear in PR reviews. Use this skill whenever a writer tags @claude on a documentation PR — not for interactive help (use doc-help for that), but for autonomous, single-shot fixes in CI." argument-hint: "[pr-number] [writer-comment]" --- @@ -21,7 +21,7 @@ You receive: Parse the writer's comment to determine what they want. Common patterns: - **Fix all issues** — apply every fix from the doc-pr review comment -- **Fix only Vale/Dale issues** — apply only linting fixes +- **Fix only Dale issues** — apply only linting fixes - **Fix a specific issue** — apply one targeted fix - **Improve flow/clarity/structure** — editorial rewrite of specific content - **Explain something** — answer a question about a flagged issue (respond in a PR comment, don't edit files) @@ -35,7 +35,7 @@ Parse the writer's comment to determine what they want. Common patterns: ```bash gh api repos/{owner}/{repo}/issues/$PR_NUMBER/comments --jq '.[] | select(.body | contains("Documentation PR Review")) | .body' | tail -1 ``` - This tells you what Vale, Dale, and the editorial review already flagged. + This tells you what Dale and the editorial review flagged. ## Step 3: Apply fixes @@ -78,7 +78,7 @@ Post a PR comment summarizing what you did: - `path/to/file.md`: - `path/to/other.md`: -Vale and Dale checks pass on all edited files. +Dale checks pass on all edited files. ``` If you were asked to explain something rather than fix it, your comment IS the deliverable — no summary needed. @@ -86,7 +86,7 @@ If you were asked to explain something rather than fix it, your comment IS the d ## Behavioral Notes - **Fix what's clear, ask about what isn't.** If a request has both obvious parts and ambiguous parts, apply the obvious fixes, commit and push those, then post a comment that summarizes what you did AND asks clarifying questions about the rest. The writer can reply with another `@claude` comment to continue. -- **Never fix issues the writer didn't ask about.** If they said "fix the Vale issues," don't also rewrite sentences for clarity. +- **Never fix issues the writer didn't ask about.** If they said "fix the Dale issues," don't also rewrite sentences for clarity. - **If a fix would substantially change the author's meaning**, skip it and explain why in your summary comment. Ask the writer how they'd like to handle it. - **If the entire request is unclear**, don't edit anything — post a comment asking for clarification. It's better to ask one good question than to guess wrong and push unwanted changes. - **Each `@claude` comment is a fresh invocation.** You won't remember previous runs, so always re-read the PR diff and review comment for context. From eaaa7335def7889dd224db19014a6b0e6fb569ed Mon Sep 17 00:00:00 2001 From: jth-nw Date: Fri, 13 Mar 2026 11:53:40 -0500 Subject: [PATCH 4/5] updated vale and dale rules --- .../skills/dale/rules/negative-assumptions.yml | 2 +- .vale/styles/Netwrix/AllowsYouTo.yml | 15 +++++++++++++++ .vale/styles/Netwrix/BoilerplateCrossRef.yml | 2 +- .vale/styles/Netwrix/ClickOn.yml | 10 ++++++++++ .vale/styles/Netwrix/CondescendingWords.yml | 10 ---------- .vale/styles/Netwrix/Contractions.yml | 17 +++++++++++++++++ .vale/styles/Netwrix/ExclamationPoints.yml | 5 ----- .vale/styles/Netwrix/FirstPerson.yml | 12 ++++++++++++ .vale/styles/Netwrix/FirstPersonPlural.yml | 1 + .vale/styles/Netwrix/HeadingPunctuation.yml | 11 +++++++++++ .vale/styles/Netwrix/HitVsClick.yml | 4 ++-- .../styles/Netwrix/ImpersonalConstructions.yml | 13 ------------- .vale/styles/Netwrix/May.yml | 7 ------- .vale/styles/Netwrix/OnceUsage.yml | 9 +++++++++ .vale/styles/Netwrix/OxfordComma.yml | 7 +++++++ .vale/styles/Netwrix/Please.yml | 2 +- .vale/styles/Netwrix/Plurals.yml | 7 +++++++ .vale/styles/Netwrix/SetupUsage.yml | 14 ++++++++++++++ .vale/styles/Netwrix/Spacing.yml | 6 ++++++ .vale/styles/Netwrix/TemporalHedges.yml | 9 +++++++++ .vale/styles/Netwrix/TypeVsEnter.yml | 10 ---------- .vale/styles/Netwrix/WishTo.yml | 2 +- .vale/styles/Netwrix/WordyPhrases.yml | 9 --------- 23 files changed, 124 insertions(+), 60 deletions(-) create mode 100644 .vale/styles/Netwrix/AllowsYouTo.yml create mode 100644 .vale/styles/Netwrix/ClickOn.yml delete mode 100644 .vale/styles/Netwrix/CondescendingWords.yml create mode 100644 .vale/styles/Netwrix/Contractions.yml delete mode 100644 .vale/styles/Netwrix/ExclamationPoints.yml create mode 100644 .vale/styles/Netwrix/FirstPerson.yml create mode 100644 .vale/styles/Netwrix/HeadingPunctuation.yml delete mode 100644 .vale/styles/Netwrix/ImpersonalConstructions.yml delete mode 100644 .vale/styles/Netwrix/May.yml create mode 100644 .vale/styles/Netwrix/OnceUsage.yml create mode 100644 .vale/styles/Netwrix/OxfordComma.yml create mode 100644 .vale/styles/Netwrix/Plurals.yml create mode 100644 .vale/styles/Netwrix/SetupUsage.yml create mode 100644 .vale/styles/Netwrix/Spacing.yml create mode 100644 .vale/styles/Netwrix/TemporalHedges.yml delete mode 100644 .vale/styles/Netwrix/TypeVsEnter.yml delete mode 100644 .vale/styles/Netwrix/WordyPhrases.yml diff --git a/.claude/skills/dale/rules/negative-assumptions.yml b/.claude/skills/dale/rules/negative-assumptions.yml index 99d6355acf..dd423f9327 100644 --- a/.claude/skills/dale/rules/negative-assumptions.yml +++ b/.claude/skills/dale/rules/negative-assumptions.yml @@ -1,3 +1,3 @@ -message: "Do not use negative assumptions about what users can or cannot do." +message: "Don't make negative assumptions about what users can or cannot do." level: warning reason: "This rule should trigger when the user or agent writes something that assumes a user cannot do something without the product." \ No newline at end of file diff --git a/.vale/styles/Netwrix/AllowsYouTo.yml b/.vale/styles/Netwrix/AllowsYouTo.yml new file mode 100644 index 0000000000..184c1797d8 --- /dev/null +++ b/.vale/styles/Netwrix/AllowsYouTo.yml @@ -0,0 +1,15 @@ +extends: existence +message: "Avoid '%s'. Rewrite with a direct imperative or active construction, e.g. 'Use X to ...' or 'With X, you can ...'." +level: warning +ignorecase: true +tokens: + - '\ballows you to\b' + - '\ballow you to\b' + - '\ballows users to\b' + - '\ballow users to\b' + - '\benables you to\b' + - '\benable you to\b' + - '\bpermits you to\b' + - '\benables users to\b' + - '\benable users to\b' + - '\bpermits users to\b' diff --git a/.vale/styles/Netwrix/BoilerplateCrossRef.yml b/.vale/styles/Netwrix/BoilerplateCrossRef.yml index c43d66276e..4785e3ee60 100644 --- a/.vale/styles/Netwrix/BoilerplateCrossRef.yml +++ b/.vale/styles/Netwrix/BoilerplateCrossRef.yml @@ -1,6 +1,6 @@ extends: existence message: "Avoid '%s'. Write specific cross-reference text that describes what the reader will find." -level: suggestion +level: warning ignorecase: true tokens: - '\bfor more information\b' diff --git a/.vale/styles/Netwrix/ClickOn.yml b/.vale/styles/Netwrix/ClickOn.yml new file mode 100644 index 0000000000..9e785c9608 --- /dev/null +++ b/.vale/styles/Netwrix/ClickOn.yml @@ -0,0 +1,10 @@ +extends: substitution +message: "Use '%s' — omit 'on' after click verbs." +level: warning +ignorecase: true +swap: + '\b(double-click) on\b': '$1' + '\b(right-click) on\b': '$1' + '\b(left-click) on\b': '$1' + '\b(left click) on\b': '$1' + '\b(click) on\b': '$1' diff --git a/.vale/styles/Netwrix/CondescendingWords.yml b/.vale/styles/Netwrix/CondescendingWords.yml deleted file mode 100644 index 714ce7b3c8..0000000000 --- a/.vale/styles/Netwrix/CondescendingWords.yml +++ /dev/null @@ -1,10 +0,0 @@ -extends: existence -message: "Avoid '%s'. Remove it or rewrite without language that minimizes task difficulty." -level: suggestion -ignorecase: true -nonword: true -tokens: - - '\bsimply\b' - - '\beasily\b' - - '\bbasically\b' - - '\bobviously\b' diff --git a/.vale/styles/Netwrix/Contractions.yml b/.vale/styles/Netwrix/Contractions.yml new file mode 100644 index 0000000000..965ad3b1b2 --- /dev/null +++ b/.vale/styles/Netwrix/Contractions.yml @@ -0,0 +1,17 @@ +extends: substitution +message: "Use the contraction '%s' instead of the formal form." +level: warning +ignorecase: true +swap: + '\bdo not\b': "don't" + '\bdoes not\b': "doesn't" + '\bdid not\b': "didn't" + '\bcannot\b': "can't" + '\bcan not\b': "can't" + '\bwould not\b': "wouldn't" + '\bshould not\b': "shouldn't" + '\bcould not\b': "couldn't" + '\bis not\b': "isn't" + '\bare not\b': "aren't" + '\bwas not\b': "wasn't" + '\bwere not\b': "weren't" diff --git a/.vale/styles/Netwrix/ExclamationPoints.yml b/.vale/styles/Netwrix/ExclamationPoints.yml deleted file mode 100644 index 9334c48272..0000000000 --- a/.vale/styles/Netwrix/ExclamationPoints.yml +++ /dev/null @@ -1,5 +0,0 @@ -extends: existence -message: "Avoid exclamation points in technical documentation." -level: warning -tokens: - - '!' diff --git a/.vale/styles/Netwrix/FirstPerson.yml b/.vale/styles/Netwrix/FirstPerson.yml new file mode 100644 index 0000000000..7871be7023 --- /dev/null +++ b/.vale/styles/Netwrix/FirstPerson.yml @@ -0,0 +1,12 @@ +extends: existence +message: "Avoid first person singular '%s'. Rewrite without 'I', 'me', 'my', 'mine', or contractions of 'I'." +level: warning +ignorecase: false +tokens: + - '\bI\b' + - '\bme\b' + - '\bmy\b' + - '\bI''m\b' + - '\bI''ve\b' + - '\bI''ll\b' + - '\bI''d\b' diff --git a/.vale/styles/Netwrix/FirstPersonPlural.yml b/.vale/styles/Netwrix/FirstPersonPlural.yml index 8c6c7677d3..475707f49e 100644 --- a/.vale/styles/Netwrix/FirstPersonPlural.yml +++ b/.vale/styles/Netwrix/FirstPersonPlural.yml @@ -7,3 +7,4 @@ tokens: - '\bwe\b' - '\bour\b' - '\bours\b' + - '\bus\b' diff --git a/.vale/styles/Netwrix/HeadingPunctuation.yml b/.vale/styles/Netwrix/HeadingPunctuation.yml new file mode 100644 index 0000000000..3c54564790 --- /dev/null +++ b/.vale/styles/Netwrix/HeadingPunctuation.yml @@ -0,0 +1,11 @@ +extends: existence +message: "Don't end headings with terminal punctuation ('%s'). Remove the trailing character." +level: warning +scope: heading +nonword: true +tokens: + - '\.$' + - ':$' + - ';$' + - '\?$' + - '!$' diff --git a/.vale/styles/Netwrix/HitVsClick.yml b/.vale/styles/Netwrix/HitVsClick.yml index 535b5ec0e6..cef6c05f69 100644 --- a/.vale/styles/Netwrix/HitVsClick.yml +++ b/.vale/styles/Netwrix/HitVsClick.yml @@ -1,6 +1,6 @@ extends: substitution -message: "Use 'click' instead of 'hit' for UI elements." -level: suggestion +message: "Use 'click' instead of 'hit' for UI elements. Don't use 'click on' — just 'click'." +level: warning ignorecase: true swap: '\bhit\b': 'click' diff --git a/.vale/styles/Netwrix/ImpersonalConstructions.yml b/.vale/styles/Netwrix/ImpersonalConstructions.yml deleted file mode 100644 index c4884ef7b6..0000000000 --- a/.vale/styles/Netwrix/ImpersonalConstructions.yml +++ /dev/null @@ -1,13 +0,0 @@ -extends: existence -message: "Avoid '%s'. Rewrite with an active subject or a direct imperative." -level: warning -ignorecase: true -tokens: - - '\bit is recommended\b' - - '\bit is necessary\b' - - '\bit is possible\b' - - '\bit is required\b' - - '\bit is advised\b' - - '\bit is suggested\b' - - '\bit is important\b' - - '\bit should be noted\b' diff --git a/.vale/styles/Netwrix/May.yml b/.vale/styles/Netwrix/May.yml deleted file mode 100644 index 5e2b6d871f..0000000000 --- a/.vale/styles/Netwrix/May.yml +++ /dev/null @@ -1,7 +0,0 @@ -extends: existence -message: "Avoid 'may'. Use 'might' for possibility or 'can' for capability." -level: suggestion -ignorecase: false -nonword: true -tokens: - - '\bmay\b' diff --git a/.vale/styles/Netwrix/OnceUsage.yml b/.vale/styles/Netwrix/OnceUsage.yml new file mode 100644 index 0000000000..b2d035e989 --- /dev/null +++ b/.vale/styles/Netwrix/OnceUsage.yml @@ -0,0 +1,9 @@ +extends: existence +message: "Avoid '%s' for temporal sequence. Use 'After' (for sequential steps) or 'When' (for conditions or events)." +level: warning +ignorecase: true +tokens: + - '\bonce you\b' + - '\bonce the\b' + - '\bonce it\b' + - '\bonce this\b' diff --git a/.vale/styles/Netwrix/OxfordComma.yml b/.vale/styles/Netwrix/OxfordComma.yml new file mode 100644 index 0000000000..40b7e9c64a --- /dev/null +++ b/.vale/styles/Netwrix/OxfordComma.yml @@ -0,0 +1,7 @@ +extends: existence +message: "'%s' appears to be missing an Oxford comma. Add a comma before 'and' or 'or' in series of three or more items." +level: warning +ignorecase: true +tokens: + - '\w+,\s+\w+\s+and\b' + - '\w+,\s+\w+\s+or\b' diff --git a/.vale/styles/Netwrix/Please.yml b/.vale/styles/Netwrix/Please.yml index 48f370a86f..b9b3da3601 100644 --- a/.vale/styles/Netwrix/Please.yml +++ b/.vale/styles/Netwrix/Please.yml @@ -4,4 +4,4 @@ level: warning ignorecase: true nonword: true tokens: - - '\bplease\b' + - '\bplease(?!\s+note)\b' diff --git a/.vale/styles/Netwrix/Plurals.yml b/.vale/styles/Netwrix/Plurals.yml new file mode 100644 index 0000000000..b5539c3f6d --- /dev/null +++ b/.vale/styles/Netwrix/Plurals.yml @@ -0,0 +1,7 @@ +extends: existence +message: "Avoid parenthetical plurals '%s'. Use the plural form, or rephrase with 'one or more'." +level: warning +ignorecase: true +nonword: true +tokens: + - '\w+\(s\)' diff --git a/.vale/styles/Netwrix/SetupUsage.yml b/.vale/styles/Netwrix/SetupUsage.yml new file mode 100644 index 0000000000..6e58e80e61 --- /dev/null +++ b/.vale/styles/Netwrix/SetupUsage.yml @@ -0,0 +1,14 @@ +extends: substitution +message: "Use '%s' — 'setup' is a noun or adjective. The verb form is 'set up' (two words)." +level: warning +ignorecase: true +swap: + '\bto setup\b': 'to set up' + '\bwill setup\b': 'will set up' + '\bcan setup\b': 'can set up' + '\bmust setup\b': 'must set up' + '\bshould setup\b': 'should set up' + '\bhave setup\b': 'have set up' + '\bhas setup\b': 'has set up' + '\bhad setup\b': 'had set up' + '\bgetting setup\b': 'getting set up' diff --git a/.vale/styles/Netwrix/Spacing.yml b/.vale/styles/Netwrix/Spacing.yml new file mode 100644 index 0000000000..6fde68afbe --- /dev/null +++ b/.vale/styles/Netwrix/Spacing.yml @@ -0,0 +1,6 @@ +extends: existence +message: "Use a single space after sentence-ending punctuation, not two." +level: warning +nonword: true +tokens: + - '[.!?]\s{2,}\S' diff --git a/.vale/styles/Netwrix/TemporalHedges.yml b/.vale/styles/Netwrix/TemporalHedges.yml new file mode 100644 index 0000000000..267a014972 --- /dev/null +++ b/.vale/styles/Netwrix/TemporalHedges.yml @@ -0,0 +1,9 @@ +extends: existence +message: "Avoid '%s'. Documentation should describe the current state without temporal qualifiers." +level: warning +ignorecase: true +nonword: true +tokens: + - '\bcurrently\b' + - '\bpresently\b' + - '\bas of this writing\b' diff --git a/.vale/styles/Netwrix/TypeVsEnter.yml b/.vale/styles/Netwrix/TypeVsEnter.yml deleted file mode 100644 index 39eccde879..0000000000 --- a/.vale/styles/Netwrix/TypeVsEnter.yml +++ /dev/null @@ -1,10 +0,0 @@ -extends: existence -message: "Use 'enter' instead of 'type' when instructing users to input text into a field." -level: suggestion -ignorecase: true -tokens: - - '\btype your\b' - - '\btype the\b' - - '\btype in\b' - - '\btype a\b' - - '\btype an\b' diff --git a/.vale/styles/Netwrix/WishTo.yml b/.vale/styles/Netwrix/WishTo.yml index 5fa1fd9b81..b37c6f0fac 100644 --- a/.vale/styles/Netwrix/WishTo.yml +++ b/.vale/styles/Netwrix/WishTo.yml @@ -1,6 +1,6 @@ extends: substitution message: "Use '%s' instead of 'wish to', or rewrite as a direct imperative." -level: suggestion +level: warning ignorecase: true swap: '\bwish to\b': 'want to' diff --git a/.vale/styles/Netwrix/WordyPhrases.yml b/.vale/styles/Netwrix/WordyPhrases.yml deleted file mode 100644 index d3aee592bb..0000000000 --- a/.vale/styles/Netwrix/WordyPhrases.yml +++ /dev/null @@ -1,9 +0,0 @@ -extends: substitution -message: "Use '%s' instead of this wordy phrase." -level: warning -ignorecase: true -swap: - '\bprior to\b': 'before' - '\bsubsequent to\b': 'after' - '\bin the event that\b': 'if' - '\bdue to the fact that\b': 'because' From a39159628c157f7c522ed49a0203107ef5184c87 Mon Sep 17 00:00:00 2001 From: jth-nw Date: Fri, 13 Mar 2026 13:26:01 -0500 Subject: [PATCH 5/5] vale linter prepush, updated skills --- .claude/skills/doc-pr-fix/SKILL.md | 18 ++- .claude/skills/doc-pr/SKILL.md | 40 ++---- .github/workflows/claude-doc-pr.yml | 8 +- .github/workflows/vale-linter.yml | 200 ++++++++++++++++++++++++++++ .husky/pre-push | 20 +-- CLAUDE.md | 14 +- CONTRIBUTING.md | 194 +++++++++++++++++++++++++++ README.md | 138 ++++++------------- docs/CLAUDE.md | 22 ++- 9 files changed, 496 insertions(+), 158 deletions(-) create mode 100644 .github/workflows/vale-linter.yml create mode 100644 CONTRIBUTING.md diff --git a/.claude/skills/doc-pr-fix/SKILL.md b/.claude/skills/doc-pr-fix/SKILL.md index 798cefef14..761b250417 100644 --- a/.claude/skills/doc-pr-fix/SKILL.md +++ b/.claude/skills/doc-pr-fix/SKILL.md @@ -1,6 +1,6 @@ --- name: doc-pr-fix -description: "Autonomous fixer for documentation PRs. Triggered by @claude comments on PRs targeting dev. Reads the writer's request and the existing doc-pr review, then applies fixes and commits. Only Dale and editorial issues appear in PR reviews. Use this skill whenever a writer tags @claude on a documentation PR — not for interactive help (use doc-help for that), but for autonomous, single-shot fixes in CI." +description: "Autonomous fixer for documentation PRs. Triggered by @claude comments on PRs targeting dev. Reads the writer's request, the doc-pr review comment, and the Vale linting comment, then applies fixes and commits. Use this skill whenever a writer tags @claude on a documentation PR — not for interactive help (use doc-help for that), but for autonomous, single-shot fixes in CI." argument-hint: "[pr-number] [writer-comment]" --- @@ -20,8 +20,9 @@ You receive: Parse the writer's comment to determine what they want. Common patterns: -- **Fix all issues** — apply every fix from the doc-pr review comment -- **Fix only Dale issues** — apply only linting fixes +- **Fix all issues** — apply every fix from the doc-pr review comment and the Vale linting comment +- **Fix only Vale issues** — apply only fixes from the Vale linting comment +- **Fix only Dale issues** — apply only Dale linting fixes - **Fix a specific issue** — apply one targeted fix - **Improve flow/clarity/structure** — editorial rewrite of specific content - **Explain something** — answer a question about a flagged issue (respond in a PR comment, don't edit files) @@ -36,13 +37,20 @@ Parse the writer's comment to determine what they want. Common patterns: gh api repos/{owner}/{repo}/issues/$PR_NUMBER/comments --jq '.[] | select(.body | contains("Documentation PR Review")) | .body' | tail -1 ``` This tells you what Dale and the editorial review flagged. +4. If the writer asks to fix Vale issues (or "all issues"), also find the Vale linting comment: + ```bash + gh api repos/{owner}/{repo}/issues/$PR_NUMBER/comments --jq '.[] | select(.user.login == "github-actions[bot]" and (.body | contains("## Vale Linting"))) | .body' | tail -1 + ``` + This gives you the Vale results table with file paths, line numbers, and rule violations. ## Step 3: Apply fixes Work through the requested fixes methodically: -- For **linting fixes** (Dale): fix each flagged issue in order, file by file -- For **editorial fixes**: apply the suggested changes from the review, or if the writer asked for something broader ("improve the flow"), read the full document and apply edits that address the request while following Netwrix style +- For **Vale fixes**: read `docs/CLAUDE.md` for Vale guidance (especially the two rules requiring extra care), then fix each flagged issue in order, file by file +- For **Dale fixes**: fix each flagged issue in order, file by file +- For **editorial fixes from the review**: apply the suggested changes from the review comment +- For **broader editorial requests** ("improve the flow", "make this clearer", "help with structure"): invoke `/doc-help` with the file path and the writer's request. Doc-help will analyze the document using its structured editing framework (structure, clarity, voice, surface). Since this is running in CI without an interactive writer, apply all of doc-help's suggestions autonomously rather than waiting for feedback - For **explanations**: post a PR comment explaining the issue and how to fix it, then stop — don't edit files When editing: diff --git a/.claude/skills/doc-pr/SKILL.md b/.claude/skills/doc-pr/SKILL.md index 868b1ef851..3d99d1f0a0 100644 --- a/.claude/skills/doc-pr/SKILL.md +++ b/.claude/skills/doc-pr/SKILL.md @@ -1,12 +1,14 @@ --- name: doc-pr -description: "Orchestrate a full documentation review for pull requests targeting dev. Runs Vale linting, Dale linting, and editorial review on changed markdown files, then posts a structured comment to the PR. Use this skill whenever a PR involves markdown files in docs/ and targets the dev branch — triggered automatically by the doc-pr GitHub Actions workflow on PR open, sync, or when invoked manually via /doc-pr." +description: "Orchestrate a documentation review for pull requests targeting dev. Runs Dale linting and editorial review on changed markdown files, then posts a structured comment to the PR. Vale linting runs separately via the vale-linter workflow (inline review comments + summary PR comment). Use this skill whenever a PR involves markdown files in docs/ and targets the dev branch — triggered automatically by the doc-pr GitHub Actions workflow on PR open, sync, or when invoked manually via /doc-pr." argument-hint: "[changed-files-csv] [pr-number]" --- # Doc PR Review -You orchestrate a three-stage documentation review pipeline for pull requests. Your job is to run each stage, collect the results, and post a single comprehensive review comment to the PR. +You orchestrate a two-stage documentation review pipeline for pull requests. Your job is to run each stage, collect the results, and post a single comprehensive review comment to the PR. + +Vale linting runs separately (via the vale-linter workflow) and posts inline review comments plus a summary PR comment. Do not run Vale or include Vale results in your review. Read `docs/CLAUDE.md` before starting — it contains the writing standards and Vale guidance you need for the editorial review stage. @@ -24,17 +26,7 @@ If the environment variables are empty, check for positional arguments (`$1` = f Split the comma-separated file list into individual file paths for processing. -## Stage 1: Vale Linting - -Run Vale on each changed file and capture the output. - -```bash -vale --output=line -``` - -Collect all Vale output. If Vale finds no issues for a file, note that file as clean. If Vale is not installed, report that Vale was unavailable and skip to Stage 2. - -## Stage 2: Dale Linting +## Stage 1: Dale Linting For each changed file, invoke the Dale linter skill: @@ -44,7 +36,7 @@ For each changed file, invoke the Dale linter skill: Dale returns a table of rule violations or a clean report. Collect all Dale output. -## Stage 3: Editorial Review +## Stage 2: Editorial Review This stage applies the doc-help editing analysis to the PR changes — but non-interactively. You are producing a written review, not having a conversation. @@ -70,7 +62,7 @@ Only report issues on lines that were added or modified in this PR. Do not flag ## Output — MANDATORY: Post as PR Comment -After completing all three stages, you MUST write the review to a file and post it as a PR comment. This is the most important step — the review is useless if it is not posted. +After completing both stages, you MUST write the review to a file and post it as a PR comment. This is the most important step — the review is useless if it is not posted. **Step 1: Write the review to a temporary file.** @@ -79,16 +71,6 @@ Write the full review body to `/tmp/doc-pr-review.md` using the Write tool. Foll ```markdown ## Documentation PR Review -### Vale Linting - -**path/to/file.md** - -| Line | Rule | Message | Offending Text | -|------|------|---------|----------------| -| N | `RuleName` | description of the issue | `offending text` | - -(Repeat for each file. Write "No issues found." if clean.) - ### Dale Linting **path/to/file.md** @@ -111,7 +93,7 @@ Write the full review body to `/tmp/doc-pr-review.md` using the Write tool. Foll ### Summary -N Vale issues, N Dale issues, N editorial suggestions across N files. +N Dale issues, N editorial suggestions across N files. Vale issues are posted in a separate comment by the vale-linter workflow. --- @@ -120,7 +102,7 @@ N Vale issues, N Dale issues, N editorial suggestions across N files. Comment `@claude` on this PR followed by your instructions to get help: - `@claude fix all issues` — fix all Vale, Dale, and editorial issues -- `@claude fix only the Vale issues` — fix just the linting problems +- `@claude fix only the Vale issues` — fix just the Vale issues - `@claude help improve the flow of this document` — get writing assistance - `@claude explain the voice issues` — understand why something was flagged @@ -140,6 +122,6 @@ If the `gh pr comment` command fails, report the error. Do NOT end your turn wit ## Behavioral Notes - Be thorough but not pedantic — focus on issues that genuinely affect reader comprehension or violate Netwrix standards -- When Vale and your editorial review flag the same issue, include it only in the Vale section (Vale is more specific) -- If a file has zero issues across all three stages, still list it with "No issues found." so the reviewer knows it was checked +- Do not flag issues that Vale or Dale already catch — focus on what linters miss +- If a file has zero issues across both stages, still list it with "No issues found." so the reviewer knows it was checked - Never modify the files — this is a read-only review diff --git a/.github/workflows/claude-doc-pr.yml b/.github/workflows/claude-doc-pr.yml index 50d6899f8a..f86e52fb45 100644 --- a/.github/workflows/claude-doc-pr.yml +++ b/.github/workflows/claude-doc-pr.yml @@ -60,9 +60,9 @@ jobs: for ID in $COMMENT_IDS; do gh api repos/${{ github.repository }}/issues/comments/${ID} -X DELETE 2>/dev/null || true done - # Dismiss previous Vale reviews so they don't pile up + # Dismiss previous bot reviews (Dale) so they don't pile up REVIEW_IDS=$(gh api repos/${{ github.repository }}/pulls/${PR_NUMBER}/reviews \ - --jq '[.[] | select(.user.login == "github-actions[bot]" and .state == "COMMENTED") | .id] | .[]' 2>/dev/null || true) + --jq '[.[] | select(.user.login == "github-actions[bot]" and .state == "COMMENTED" and (.body | contains("Dale found"))) | .id] | .[]' 2>/dev/null || true) for ID in $REVIEW_IDS; do gh api repos/${{ github.repository }}/pulls/${PR_NUMBER}/reviews/${ID}/dismissals \ -f message="Superseded by new review" -f event="DISMISS" 2>/dev/null || true @@ -167,7 +167,7 @@ jobs: - Dale issues: ${{ steps.dale-post.outputs.dale_count }} (already posted as inline comments) - PR diff is at: /tmp/pr-diff.txt - NOTE: Vale linting is enforced by a pre-push hook. Writers resolve Vale issues locally before pushing. Do not flag or mention Vale issues in the review. + NOTE: Vale linting runs separately (vale-linter workflow) and posts inline review comments plus a summary PR comment. Do not run Vale or include Vale issues in this review. INSTRUCTIONS: @@ -274,7 +274,7 @@ jobs: show_full_output: true prompt: | /doc-pr-fix ${{ steps.pr-info.outputs.number }} $COMMENT_BODY - claude_args: '--allowedTools "Bash(gh:*),Bash(git:*),Read,Write,Edit,Glob,Grep,Skill(doc-pr-fix),Skill(dale)"' + claude_args: '--allowedTools "Bash(gh:*),Bash(git:*),Read,Write,Edit,Glob,Grep,Skill(doc-pr-fix),Skill(dale),Skill(doc-help)"' - name: Resolve Dale inline comments if: steps.pr-info.outputs.is_fork == 'false' && steps.pr-info.outputs.targets_dev == 'true' diff --git a/.github/workflows/vale-linter.yml b/.github/workflows/vale-linter.yml new file mode 100644 index 0000000000..b75a4d1d05 --- /dev/null +++ b/.github/workflows/vale-linter.yml @@ -0,0 +1,200 @@ +name: Vale Linter + +on: + pull_request: + types: [opened, synchronize] + branches: + - dev + paths: + - 'docs/**/*.md' + - '!docs/**/CLAUDE.md' + - '!docs/**/SKILL.md' + +jobs: + vale-lint: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 1 + + - name: Get changed markdown files + id: changed-files + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_NUMBER=${{ github.event.pull_request.number }} + CHANGED_MD_FILES=$(gh pr diff "$PR_NUMBER" --name-only | grep -E '^docs/.*\.md$' | grep -v '/CLAUDE\.md$' | grep -v '/SKILL\.md$' || true) + if [ -z "$CHANGED_MD_FILES" ]; then + echo "No docs markdown files changed" + echo "count=0" >> "$GITHUB_OUTPUT" + else + echo "Changed markdown files:" + echo "$CHANGED_MD_FILES" + echo "count=$(echo "$CHANGED_MD_FILES" | wc -l | tr -d ' ')" >> "$GITHUB_OUTPUT" + echo "$CHANGED_MD_FILES" > /tmp/changed-files.txt + fi + + - name: Install Vale + if: steps.changed-files.outputs.count > 0 + run: | + wget -q https://github.com/errata-ai/vale/releases/download/v3.9.5/vale_3.9.5_Linux_64-bit.tar.gz -O /tmp/vale.tar.gz + tar -xzf /tmp/vale.tar.gz -C /tmp + chmod +x /tmp/vale + /tmp/vale --version + + - name: Run Vale on changed files + id: vale + if: steps.changed-files.outputs.count > 0 + run: | + # Collect all Vale violations into a single JSON array + jq -n '[]' > /tmp/vale-all.json + + while IFS= read -r file; do + if [ -f "$file" ]; then + RESULT=$(/tmp/vale --output JSON "$file" 2>/dev/null || true) + if [ -n "$RESULT" ] && [ "$RESULT" != "{}" ]; then + echo "$RESULT" | jq --arg f "$file" ' + [to_entries[] | .value[] | {path: $f, line: .Line, check: .Check, message: .Message}] + ' > /tmp/vale-file.json + jq -s '.[0] + .[1]' /tmp/vale-all.json /tmp/vale-file.json > /tmp/vale-tmp.json + mv /tmp/vale-tmp.json /tmp/vale-all.json + fi + fi + done < /tmp/changed-files.txt + + TOTAL=$(jq 'length' /tmp/vale-all.json) + echo "total_issues=$TOTAL" >> "$GITHUB_OUTPUT" + echo "Vale found $TOTAL issue(s)" + + - name: Parse diff for inline comment eligibility + id: diff-lines + if: steps.vale.outputs.total_issues > 0 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_NUMBER=${{ github.event.pull_request.number }} + gh pr diff "$PR_NUMBER" > /tmp/pr-diff.txt + + # Extract lines visible in the diff per file (file:line format) + awk ' + /^\+\+\+ b\// { file = substr($0, 7) } + /^@@ / { + for (i = 1; i <= NF; i++) { + if ($i ~ /^\+[0-9]/) { + sub(/^\+/, "", $i) + split($i, range, ",") + start = range[1] + 0 + count = (range[2] != "" ? range[2] + 0 : 1) + for (j = start; j < start + count; j++) { + print file ":" j + } + break + } + } + } + ' /tmp/pr-diff.txt | sort -u > /tmp/diff-lines.txt + + echo "Diff lines extracted: $(wc -l < /tmp/diff-lines.txt)" + + - name: Build inline comments + id: inline + if: steps.vale.outputs.total_issues > 0 + run: | + # Filter violations to those on diff lines, take first 25 + jq -c '.[]' /tmp/vale-all.json | while read -r violation; do + KEY=$(echo "$violation" | jq -r '"\(.path):\(.line)"') + if grep -qxF "$KEY" /tmp/diff-lines.txt 2>/dev/null; then + echo "$violation" | jq '{ + path: .path, + line: .line, + side: "RIGHT", + body: ("**Vale** (`" + .check + "`): " + .message) + }' + fi + done | jq -s '.[0:25]' > /tmp/inline-comments.json + + INLINE_COUNT=$(jq 'length' /tmp/inline-comments.json) + echo "inline_count=$INLINE_COUNT" >> "$GITHUB_OUTPUT" + echo "Eligible inline comments: $INLINE_COUNT" + + - name: Build summary comment + if: steps.vale.outputs.total_issues > 0 + run: | + TOTAL=${{ steps.vale.outputs.total_issues }} + + # Build per-file tables + : > /tmp/vale-body.md + jq -r '[.[] | .path] | unique | .[]' /tmp/vale-all.json | while read -r filepath; do + { + echo "**${filepath}**" + echo "" + echo "| Line | Rule | Message |" + echo "|------|------|---------|" + jq -r --arg f "$filepath" ' + .[] | select(.path == $f) + | "| \(.line) | `\(.check)` | \(.message | gsub("\\|"; "\\\\|")) |" + ' /tmp/vale-all.json + echo "" + } >> /tmp/vale-body.md + done + + { + echo "## Vale Linting" + echo "" + echo "**Vale found ${TOTAL} issue(s)** across the changed files." + echo "" + cat /tmp/vale-body.md + echo "---" + echo "" + echo 'Fix these issues locally with `vale ` and push again. Comment `@claude fix only the Vale issues` to have them fixed automatically.' + } > /tmp/vale-comment.md + + - name: Delete previous Vale comments and reviews + if: steps.vale.outputs.total_issues > 0 || steps.changed-files.outputs.count > 0 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_NUMBER=${{ github.event.pull_request.number }} + REPO=${{ github.repository }} + + # Delete previous Vale PR comments + COMMENT_IDS=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" --jq '[.[] | select(.user.login == "github-actions[bot]" and (.body | contains("## Vale Linting"))) | .id] | .[]' 2>/dev/null || true) + for ID in $COMMENT_IDS; do + gh api "repos/${REPO}/issues/comments/${ID}" -X DELETE 2>/dev/null || true + done + + # Dismiss previous Vale reviews + REVIEW_IDS=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}/reviews" --jq '[.[] | select(.user.login == "github-actions[bot]" and (.body | contains("Vale found"))) | .id] | .[]' 2>/dev/null || true) + for ID in $REVIEW_IDS; do + gh api "repos/${REPO}/pulls/${PR_NUMBER}/reviews/${ID}/dismissals" -f message="Superseded by new review" -f event="DISMISS" 2>/dev/null || true + done + + - name: Post inline review + if: steps.inline.outputs.inline_count > 0 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_NUMBER=${{ github.event.pull_request.number }} + REPO=${{ github.repository }} + TOTAL=${{ steps.vale.outputs.total_issues }} + + jq -n \ + --arg body "**Vale found ${TOTAL} issue(s).** See inline comments below. Full summary in the PR comment." \ + --argjson comments "$(cat /tmp/inline-comments.json)" \ + '{"body": $body, "event": "COMMENT", "comments": $comments}' \ + | gh api "repos/${REPO}/pulls/${PR_NUMBER}/reviews" --input - 2>&1 + + - name: Post summary comment + if: steps.vale.outputs.total_issues > 0 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_NUMBER=${{ github.event.pull_request.number }} + REPO=${{ github.repository }} + gh pr comment "$PR_NUMBER" --repo "$REPO" --body-file /tmp/vale-comment.md diff --git a/.husky/pre-push b/.husky/pre-push index 46fe52a6ee..c48c86560d 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,24 +1,24 @@ #!/bin/sh # Vale pre-push hook -# Runs Vale on changed docs/*.md files before allowing a push. -# Blocks the push if Vale is not installed or if errors are found. +# Runs Vale on changed docs/*.md files before pushing. +# Currently in warning mode — shows errors but does not block the push. # Check if Vale is installed if ! command -v vale >/dev/null 2>&1; then echo "" - echo "ERROR: Vale is not installed." + echo "WARNING: Vale is not installed." echo "" - echo "Vale is required to push documentation changes. Install it using one of these methods:" + echo "Vale checks your writing against Netwrix style rules. Install it using one of these methods:" echo "" echo " macOS: brew install vale" echo " Linux: sudo snap install vale" echo " Windows: choco install vale" echo " Manual: Download from https://github.com/errata-ai/vale/releases" echo "" - echo "After installing, run 'vale --version' to verify, then try your push again." + echo "After installing, run 'vale --version' to verify." echo "" - exit 1 + exit 0 fi # Pre-push hook receives: as args @@ -71,10 +71,12 @@ while IFS= read -r FILE; do done <<< "$CHANGED_MD_FILES" if [ "$VALE_FAILED" -eq 1 ]; then - echo "Push blocked: Vale found errors in the files above." - echo "Fix the issues and try again." + echo "Vale found errors in the files above." + echo "Please fix these issues before or after pushing." echo "" - exit 1 + # Warning mode: don't block the push + # TODO: Change to 'exit 1' once the team is comfortable with Vale + exit 0 fi echo "Vale passed on all changed documentation files." diff --git a/CLAUDE.md b/CLAUDE.md index 6b36ad1a6b..ea1a443071 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -52,9 +52,9 @@ The build requires 16GB heap (`NODE_OPTIONS=--max-old-space-size=16384`, set aut - Sidebars: `sidebars//.js` — auto-generated, rarely need manual editing - Edits to one version do not propagate to others -### Knowledge base +### Knowledge Base -`docs/kb/` is the canonical source for KB articles. The `scripts/copy-kb-to-versions.mjs` script copies KB content into versioned product folders at build time (runs as `prestart`/`prebuild`). Never manually copy KB files — they're gitignored in versioned folders. Use `kb_allowlist.json` to control which products get KB content. +`docs/kb/` is the canonical source for Knowledge Base (KB) articles. The `scripts/copy-kb-to-versions.mjs` script copies KB content into versioned product folders at build time (runs as `prestart`/`prebuild`). Never manually copy KB files — they're gitignored in versioned folders. Use `kb_allowlist.json` to control which products get KB content. ### Static assets @@ -69,8 +69,8 @@ PRs target `dev`. Never commit directly to `dev` or `main`. The `sync-dev-to-mai | Workflow | Trigger | Purpose | |---|---|---| | `build-and-deploy.yml` | Push to main/dev, PRs to dev | Build and deploy to Azure | -| `vale-linter.yml` | PRs with `.md` changes | Vale style checks as PR review comments | -| `claude-doc-pr.yml` | PRs to dev with `docs/` changes | Vale + Dale + editorial review; `@claude` follow-up | +| `vale-linter.yml` | PRs with `.md` changes | Vale inline review comments (up to 25) + summary PR comment | +| `claude-doc-pr.yml` | PRs to dev with `docs/` changes | Dale + editorial review; `@claude` follow-up | | `claude-documentation-reviewer.yml` | PRs with `.md` changes | AI review with inline suggestions | | `claude-documentation-fixer.yml` | `@claude` comment on PR | Apply fixes and push | | `claude-issue-labeler.yml` | Issues opened/edited | Security screening, CoC check, auto-labeling | @@ -81,11 +81,15 @@ PRs target `dev`. Never commit directly to `dev` or `main`. The `sync-dev-to-mai Skills (`.claude/skills/`) are invoked with `/skill-name`. Agents (`.claude/agents/`) are autonomous workers launched via the Agent tool. +When a user asks for help with documentation, always use the appropriate tool: +- **`/doc-help` skill** — Interactive tasks: reviewing content, suggesting improvements, discussing structure or flow, brainstorming, explaining style rules, or any back-and-forth conversation about writing. +- **`tech-writer` agent** — Autonomous end-to-end tasks: drafting new documents, rewriting files, fixing all Vale errors, or editing for style and clarity. + | Component | Type | Purpose | |---|---|---| | `/dale` | Skill | Custom linter for Netwrix-specific writing patterns | | `/doc-help` | Skill | Interactive writing assistant (terminal sessions) | -| `/doc-pr` | Skill | Automated PR review (Vale + Dale + editorial) | +| `/doc-pr` | Skill | Automated PR review (Dale + editorial) | | `/doc-pr-fix` | Skill | Autonomous PR fixer triggered by `@claude` | | `tech-writer` | Agent | Autonomous end-to-end doc writing/editing | | `vale-rule-writer` | Agent | Creates new Vale rules | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..2f1cafc224 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,194 @@ +# Contributing to Netwrix Documentation + +Thank you for contributing to Netwrix product documentation. This guide covers everything you need to get started. + +## Prerequisites + +- **Node.js 22+** +- **npm** +- **Git** +- **Vale** (style linter — required for pushing documentation changes) + +### Install Vale + +[Vale](https://vale.sh/) is a command-line linter for prose. It checks your writing against a set of style rules — like a spell checker, but for grammar, word choice, and tone. Vale is required to push changes to documentation files. The pre-push hook runs Vale automatically and blocks pushes that have linting errors. + +**macOS:** +```bash +brew install vale +``` + +**Linux:** +```bash +sudo snap install vale +``` + +**Windows:** +```bash +choco install vale +``` + +**Manual install for any platform:** + +Download the latest release from [github.com/errata-ai/vale/releases](https://github.com/errata-ai/vale/releases), extract the binary, and add it to your PATH. + +Verify the installation: +```bash +vale --version +``` + +## Getting started + +```bash +# Clone the repository +git clone https://github.com/netwrix/docs.git +cd docs + +# Install dependencies (also sets up the pre-push hook via Husky) +npm install + +# Start development server +npm run start +``` + +The dev server runs on port 4500 with hot reload — changes you make to documentation files appear immediately in the browser. + +## Workflow + +1. Create a branch from `dev` (never commit directly to `dev` or `main`). +2. Make your changes to documentation files in `docs/`. +3. Run Vale on your changed files to catch linting errors. +4. Test the build with `npm run build`. +5. Push your branch (the pre-push hook runs Vale automatically and warns about any errors). +6. Create a pull request (PR) targeting `dev`. + +After you open a PR, an automated review runs Vale linting, Dale linting (AI-powered), and an editorial review, then posts the results as PR comments. If you want help applying the suggested fixes, comment `@claude` on the PR followed by your request and Claude will apply fixes and push a commit. If Claude needs clarification, it will ask in the PR comments. + +## Writing standards + +See `netwrix_style_guide.md` in the project root for the full style guide covering voice, tone, formatting, and terminology. + +### Images + +- **Location**: `static/img/product_docs//` +- **Format**: `.webp` +- **Paths**: Always absolute from project root + +```markdown +![Description](/img/product_docs/productname/image.webp) +``` + +### Frontmatter + +Every documentation page needs frontmatter: + +```yaml +--- +title: 'Page Title' +sidebar_label: 'Sidebar Label' +description: 'SEO description' +--- +``` + +## Linting with Vale + +Vale enforces 30 Netwrix-specific rules covering word choice, punctuation, formatting, and common writing issues. The pre-push hook runs Vale automatically and warns about errors in changed `docs/*.md` files. + +Run Vale on a file before pushing to catch issues early: + +```bash +vale docs/path/to/file.md +``` + +Run Vale on all changed files compared to dev: + +```bash +git diff --name-only dev | grep '^docs/.*\.md$' | xargs vale +``` + +## File structure + +- `docs///` — Versioned product documentation (e.g., `docs/accessanalyzer/12.0/`) +- `docs//` — Single-version (SaaS) products +- `docs/kb/` — Knowledge base articles (canonical source; never manually copy into versioned folders) +- `static/img/product_docs//` — Product images +- `sidebars//.js` — Sidebar configs (auto-generated; rarely need manual editing) + +Edits to one version don't propagate to others. Update each version that needs the change explicitly. + +## Available commands + +```bash +npm run start # Dev server on port 4500 +npm run start-chok # Dev server with polling (for network drives) +npm run build # Full production build +npm run clear # Clear Docusaurus cache (fixes stale build issues) +npm run serve # Serve production build +``` + +### Building a single product + +You can build or run documentation for a single product using the `DOCS_PRODUCT` environment variable, which speeds up development: + +**Windows (PowerShell):** +```powershell +$ENV:DOCS_PRODUCT="pingcastle" +npm run start +``` + +**Unix/Linux/macOS:** +```bash +export DOCS_PRODUCT="pingcastle" +npm run start +``` + +Available product IDs are in `src/config/products.js`. + +## Using Claude Code + +If you have [Claude Code](https://claude.ai/code) installed, this repository includes skills and agents that can help with documentation work. These are entirely optional — you don't need Claude Code to contribute. + +### Linting with Dale + +Dale is an AI linter that runs automatically during PR review. It catches context-dependent issues that regex-based Vale can't — passive voice, misplaced modifiers, idioms, wordiness, and other patterns. Dale results are posted as PR comments alongside the editorial review. + +Run Dale locally on any markdown file to catch context-dependent writing issues before pushing: + +``` +/dale docs/path/to/file.md +``` + +### Get interactive writing help + +Ask Claude Code for help with your writing — brainstorming document structure, drafting a section, editing existing content, or understanding a style rule. Claude automatically uses the `doc-help` skill when you ask for writing assistance. You can also invoke it directly with `/doc-help`, followed by your request. + +### Run autonomous documentation tasks + +For well-defined tasks where the work is clear and doesn't need back-and-forth, Claude automatically uses the `tech-writer` agent to handle the work end-to-end. + +Examples: +- "Fix all the Vale errors in `docs/accessanalyzer/12.0/install.md`". +- "Edit this procedure for clarity and Netwrix style". +- "Draft the installation steps based on this outline: [outline]" + +For tasks that need design decisions first — like writing a new guide from scratch — Claude uses doc-help to brainstorm the structure with you, then hands the drafting off to the tech-writer agent. + +### Quick reference + +| Task | Tool | +|---|---| +| Quick lint check on a file | `/dale docs/path/to/file.md` | +| Plan structure for a new document | `doc-help` then `tech-writer` agent | +| Review or improve existing content | `doc-help` | +| Fix Vale errors across a file | `tech-writer` agent | +| Edit a file for style and clarity | `tech-writer` agent | +| Understand a style rule or convention | `doc-help` | + +## Common Mistakes + +- Don't manually copy KB content into versioned product folders — it's managed by the KB script. +- Don't ignore Vale warnings before pushing — fix errors to keep documentation clean. +- Don't commit directly to `dev` or `main` — create a branch from `dev` first. +- Don't target `main` in PRs — always use `dev`. +- Don't use first person anywhere in documentation content. +- Don't omit examples — every concept introduced needs one. diff --git a/README.md b/README.md index 39099e0499..516bf05e52 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A centralized documentation site for all Netwrix security products, built with [Docusaurus v3.8.1](https://docusaurus.io/) and a simple prouct configuration for easy maintenance. -## 🎯 Overview +## Overview This documentation site serves all Netwrix product documentation. @@ -21,7 +21,7 @@ This documentation site serves all Netwrix product documentation. - **Centralized Configuration** - single source of truth for all product docs - **Search** capabilities with Algolia -## 🚀 Quick Start +## Quick Start ### Prerequisites @@ -32,7 +32,7 @@ This documentation site serves all Netwrix product documentation. ### Install Vale -Vale is required to push changes to documentation files. The pre-push hook runs Vale automatically and blocks pushes that have linting errors. +[Vale](https://vale.sh/) is a command-line linter for prose. A linter checks your writing against a set of style rules — like a spell checker, but for grammar, word choice, and tone. Vale is required to push changes to documentation files. The pre-push hook runs Vale automatically and blocks pushes that have linting errors. **macOS:** ```bash @@ -86,17 +86,30 @@ Run Vale on all changed docs files: git diff --name-only dev | grep '^docs/.*\.md$' | xargs vale ``` -## 📁 Project Structure +## Project Structure ``` +├── .claude/ # Claude Code configuration +│ ├── skills/ # Skills (invoked with /skill-name) +│ │ ├── dale/ # AI linter for docs +│ │ │ └── rules/ # Dale rule definitions (YAML) +│ │ ├── doc-pr/ # Automated PR review +│ │ ├── doc-pr-fix/ # Autonomous PR fixer (@claude) +│ │ └── doc-help/ # Interactive writing assistant +│ └── agents/ # Autonomous worker agents +├── .husky/ +│ └── pre-push # Vale pre-push hook (blocks on lint errors) +├── .vale/ +│ └── styles/ +│ └── Netwrix/ # 30 Vale linting rules (YAML) ├── src/ │ ├── config/ -│ │ └── products.js # CENTRALIZED CONFIGURATION +│ │ └── products.js # CENTRALIZED CONFIGURATION │ ├── components/ -│ │ ├── HomepageFeatures/ # Dynamic product grid homepage (auto-generated) -│ │ ├── ProductMetaTags/ # Search meta tags (auto-generated) -│ │ ├── CommunityHighlights/ # Community section -│ │ └── CommunityShowcase/ # Community section +│ │ ├── HomepageFeatures/ # Dynamic product grid homepage (auto-generated) +│ │ ├── ProductMetaTags/ # Search meta tags (auto-generated) +│ │ ├── CommunityHighlights/ # Community section +│ │ └── CommunityShowcase/ # Community section │ ├── css/ │ │ └── custom.css # Theme customization │ └── pages/ @@ -118,16 +131,16 @@ git diff --name-only dev | grep '^docs/.*\.md$' | xargs vale │ │ └── 12.0.js │ └── [other product sidebars]/ ├── scripts/ # Development utilities - ├── static/ # Static assets │ └── img/ │ ├── branding/ # Logos and brand assets │ └── product_docs/ # Product images +├── .vale.ini # Vale configuration ├── docusaurus.config.js # Main config └── package.json ``` -## 🛠️ Development +## Development ### Available Commands @@ -162,8 +175,6 @@ npm run start This works with any command (`start`, `start-chok`, `build`) and speeds up development when working on a single product. Available product IDs can be found in `src/config/products.js`. -*Note: you may get a warning when you first run this. This warning doesn't seem to appear again and the site worked as expected* - ### Development Workflow The centralized system makes development simple: @@ -173,11 +184,11 @@ The centralized system makes development simple: 3. **Hot reload** automatically updates the site 4. **All products and versions** work seamlessly -## ⚙️ Centralized Configuration System +## Centralized Configuration System ### Global Product Config: `src/config/products.js` -All product configuration for _building the site_ (e.g. naming) is managed in a single file. Here's how it works: +All product configuration for building the site, such as naming, is managed in a single file. Here's how it works: ```javascript // Define a product once @@ -207,11 +218,11 @@ All product configuration for _building the site_ (e.g. naming) is managed in a **Automatically generates**: -- ✅ Docusaurus plugin configurations -- ✅ Homepage product grid -- ✅ SEO meta tags -- ✅ URL routing -- ✅ Version management +- Docusaurus plugin configurations +- Homepage product grid +- SEO meta tags +- URL routing +- Version management ### Configuration Schema @@ -245,9 +256,9 @@ Key CSS variables in `src/css/custom.css`: --ifm-font-family-base: 'Inter'; /* Primary font */ ``` -## 📊 Adding New Products & Versions +## Adding New Products & Versions -### 🆕 Adding a New Product +### Adding a New Product The centralized system makes adding products incredibly simple: @@ -299,7 +310,7 @@ export default sidebars; **That's it!** The new product automatically appears on the homepage with proper routing. -### 📈 Adding a New Version +### Adding a New Version **1. Update the product in `src/config/products.js`:** @@ -346,7 +357,7 @@ export default sidebars; " > sidebars/[newproduct].js ``` -### 🏷️ Adding Product Categories +### Adding Product Categories Add to the `PRODUCT_CATEGORIES` in `src/config/products.js` for the product category it belongs to: @@ -359,86 +370,15 @@ Add to the `PRODUCT_CATEGORIES` in `src/config/products.js` for the product cate } ``` -## 📝 Content Guidelines - -### Example Documentation Structure - -``` -docs/productname/ -├── index.md # Product overview -├── getting-started/ # Quick start guides -├── user-guide/ # End user documentation -├── administration/ # Admin guides -├── api-reference/ # API documentation -└── troubleshooting/ # Common issues -``` - -### Frontmatter Template - -```yaml ---- -title: 'Page Title' -sidebar_label: 'Sidebar Label' -description: 'SEO description' ---- -``` - -### Image Guidelines - -- **Location**: `/static/img/product_docs/productname/` -- **Format**: Use `.webp` for performance -- **Paths**: Always absolute from project root - -```markdown -![Description](/img/product_docs/productname/image.webp) -``` - -## 🚀 Deployment - -### Automatic Deployment +## Deployment - **Production**: Auto-deploys from `main` branch - **Development**: Auto-deploys from `dev` branch -## 🤝 Contributing - -### Simplified Development Workflow - -Contributing is easy: - -1. **Start development server**: - -```bash -npm run start -``` - -2. **Make your changes** to documentation or configuration - -3. **Test builds**: - -```bash -npm run build -``` - -4. **Submit pull request** - -Create a PR to the dev branch, and then main when ready for production. - -## 🔍 Testing & Quality Assurance - -### Common Testing Scenarios - -- ✅ **Homepage loads** with all product categories -- ✅ **Product pages accessible** from homepage links -- ✅ **Version badges work** for multi-version products - -## 📚 Resources +## Contributing -- **Docusaurus Documentation**: [docusaurus.io](https://docusaurus.io/) -- **MDX Guide**: [mdxjs.com](https://mdxjs.com/) -- **React Documentation**: [react.dev](https://react.dev/) -- **Algolia Documentation**: [algolia.com](https://www.algolia.com/doc/) +See [CONTRIBUTING.md](CONTRIBUTING.md) for the full guide — writing standards, linting, workflow, and available tools. -## 📄 License +## License This documentation site is MIT licensed and open source, and is maintained by Netwrix Corporation. diff --git a/docs/CLAUDE.md b/docs/CLAUDE.md index d9c4cff820..1b305e1c17 100644 --- a/docs/CLAUDE.md +++ b/docs/CLAUDE.md @@ -60,15 +60,17 @@ The four core qualities: - Examples immediately follow the concept they illustrate - Never skip heading levels -## Vale +## Linting -**Always run Vale before finishing any documentation edit. Run iteratively until zero errors.** +### Vale (pre-push) + +Vale runs via a pre-push hook that checks changed `docs/*.md` files and reports errors. The hook currently runs in warning mode — it shows errors but does not block the push. Always run Vale locally and fix all errors before pushing. ```bash vale ``` -Vale enforces 26 Netwrix-specific rules in `.vale/styles/Netwrix/`. Three require extra care: +Vale enforces 30 Netwrix-specific rules in `.vale/styles/Netwrix/` covering word choice, punctuation, formatting, and common writing issues. Two rules require extra care: - **`NoteThat`** — Replace "Note that..." or "Please note..." with an admonition block: ```md @@ -80,7 +82,11 @@ Vale enforces 26 Netwrix-specific rules in `.vale/styles/Netwrix/`. Three requir - **`BoilerplateCrossRef`** and **`WeakLinkText`** — Read the surrounding context and the link destination before rewriting. The fix must reflect what the reader will actually find at the destination. -- **`ImpersonalConstructions`** — Restructure with an active subject rather than simply removing the flagged phrase. +### Dale (PR review) + +Dale is an AI linter that runs during PR review. It catches issues that regex-based Vale rules can't — passive voice, misplaced modifiers, idioms, wordiness, and other context-dependent patterns. Dale rules are in `.claude/skills/dale/rules/`. + +Run Dale locally with `/dale `. ## Content Patterns @@ -118,9 +124,11 @@ Each step is a single action. Lead with the UI element or command: ## CI/CD Context -**Vale linter** — Runs on every PR touching `.md` files. Posts inline review comments. Does not block merges. +**Vale (pre-push hook)** — Runs automatically before every push. Currently in warning mode — shows errors but does not block the push. Writers should fix all Vale errors locally before pushing. + +**Vale (PR review)** — Runs on PRs to `dev` with docs changes. Posts up to 25 inline review comments on changed lines plus a summary PR comment. -**Doc reviewer** — Runs on every PR. Reads this file first, then categorizes issues as "in PR changes" vs. "preexisting" and posts inline suggestions reviewers can apply with one click. +**Doc PR review** — Runs on PRs to `dev` with docs changes. Posts Dale inline comments and an editorial review summary. Does not block merges. **Doc fixer** — Triggered by an `@claude` comment on a PR. Applies fixes and pushes. Fork PRs cannot be pushed to. @@ -131,7 +139,7 @@ Each step is a single action. Lead with the UI element or command: ## Common Mistakes - Don't manually copy KB content into versioned product folders — it's managed by the KB script -- Don't skip Vale before submitting — it runs in CI regardless +- Don't ignore Vale warnings before pushing — fix errors to keep documentation clean - Don't commit directly to `dev` or `main` — create a branch from `dev` first - Don't target `main` in PRs — use `dev` - Don't use first person anywhere in documentation content