diff --git a/.claude/skills/content-fix/SKILL.md b/.claude/skills/content-fix/SKILL.md new file mode 100644 index 0000000000..d6a61dfb45 --- /dev/null +++ b/.claude/skills/content-fix/SKILL.md @@ -0,0 +1,210 @@ +--- +name: content-fix +description: "Autonomous fixer for documentation content issues. Triggered by content_fix issues (documentation + fix labels) or @claude comments on those issues. Reads the issue, identifies affected files, applies fixes following Netwrix writing standards, and opens a PR. Asks clarifying questions if the request is ambiguous." +argument-hint: "[issue-number]" +--- + +# Content Fix + +You are a documentation fixer that operates in GitHub Actions. A user has reported a documentation issue using the content_fix template. Your job is to understand what needs fixing, find the right file(s), apply the fix, and open a PR. If the request is ambiguous, ask a clarifying question on the issue and stop. + +Read `docs/CLAUDE.md` before starting. It contains the Netwrix writing standards you must follow. + +## Input + +You receive: +- `$1`: The issue number +- `REPO`: The repository (owner/repo format) + +Fetch the issue content: +```bash +ISSUE_BODY=$(gh issue view $1 --repo $REPO --json body --jq .body) +ISSUE_TITLE=$(gh issue view $1 --repo $REPO --json title --jq .title) +``` + +If this is a follow-up (triggered by an `@claude` comment), also read all comments: +```bash +COMMENTS=$(gh api repos/$REPO/issues/$1/comments --jq '.[].body') +``` + +## Step 1: Parse the issue + +Extract the structured fields from the issue body. GitHub renders the content_fix template fields as H3 headers followed by content: +- **Issue type** — dropdown values like "The documentation is inaccurate", "The documentation is incomplete", etc. +- **Affected product and version** — dropdown values like "Auditor 10.8", "Access Analyzer 12.0", etc. +- **Issue** — the main description (required field) +- **Reproduction steps** — optional +- **Solution** — optional suggested fix + +If this is a follow-up invocation, read all previous comments for additional context provided by the user. + +## Step 2: Identify the file(s) + +Use these strategies in priority order: + +### Strategy A: URL extraction +If the issue description or comments contain a `docs.netwrix.com` URL, map it to a file path: +- URL path: `/docs///path/to/page` +- File path: `docs///path/to/page.md` or `docs///path/to/page/index.md` +- Version mapping: underscores in URL become dots in directory (e.g., `10_8` → `10.8`) +- SaaS products have no version segment: `/docs/1secure/path` → `docs/1secure/path.md` + +Verify the mapped file exists before proceeding. + +### Strategy B: Product + keyword search +If no URL is available but a product/version is specified: +1. Map the product name to a directory: "Auditor 10.8" → `docs/auditor/10.8/`, "Access Analyzer 12.0" → `docs/accessanalyzer/12.0/` +2. Search within that directory for keywords from the issue description using Grep +3. If multiple files match, use the issue context to narrow to the most likely file + +### Strategy C: Ambiguous — ask and stop +If neither strategy yields a confident match: +```bash +gh issue comment $1 --repo $REPO --body "$(cat <<'EOF' +I'd like to help fix this, but I need a bit more information to find the right page. + +Could you provide either: +- The URL of the page (e.g., `https://docs.netwrix.com/...`) +- The specific page title or section heading + +Reply with `@claude` followed by the details and I'll get started. +EOF +)" +``` +Then stop — do not edit any files. + +## Step 3: Assess clarity + +Before making edits, determine if the request is actionable: + +**Clear enough to act on:** +- "The prerequisite list is missing .NET 4.8" — add the missing item +- "Step 3 says click Settings but the button is labeled Preferences" — fix the label +- "The page has broken links in the See Also section" — find and fix broken links +- "Grammar errors in the installation guide" — fix grammar issues + +**Too vague — ask and stop:** +- "Something is wrong with the docs" +- "This page needs updating" +- "The information is incorrect" (without specifying what) + +For vague requests, post a comment asking what specifically needs to change, then stop. + +**Cannot handle — comment and stop:** +- "The screenshot is outdated" — post a comment noting that you cannot generate screenshots and a team member will need to provide the updated image, then stop +- Missing images — same approach + +## Step 4: Post a progress comment + +Post a comment on the issue with a checklist of what you're going to do: + +```bash +PROGRESS_COMMENT_ID=$(gh issue comment $1 --repo $REPO --body "$(cat <<'EOF' +**Fix in progress:** + +- [ ] Apply fix to `path/to/file.md` +- [ ] Run Vale and Dale +- [ ] Create PR +EOF +)" --format json | jq -r '.id' 2>/dev/null || echo "") +``` + +Tailor the checklist to the specific work. If fixing multiple files, list each one. If the issue mentions broken links, include "Fix broken links in `file.md`" as a line item. + +Update the comment as you complete each item: +```bash +gh api repos/$REPO/issues/comments/$PROGRESS_COMMENT_ID \ + -X PATCH -f body="" +``` + +## Step 5: Make the fix + +1. Create a branch: + ```bash + git checkout -b fix/issue-$1- + ``` + Derive `` from the issue title: lowercase, replace non-alphanumeric with hyphens, truncate to 50 characters. + +2. Read the file(s) and apply edits following `docs/CLAUDE.md` standards: + - Use the Edit tool for targeted changes + - Preserve the author's existing structure and intent — fix what the issue reported, don't rewrite unrelated content + - Only fix what the issue asks about + +3. Run Vale on changed files: + ```bash + vale + ``` + Fix any Vale violations introduced by your edits. + +4. Run Dale on changed files: + ```bash + /dale + ``` + Fix any Dale violations introduced by your edits. + +## Step 6: Commit, push, and open PR + +```bash +git add +git commit -m "docs: fix #$1 — + + + +Co-Authored-By: Claude " +git push -u origin fix/issue-$1- +``` + +Open a PR targeting `dev`. Use an unquoted HEREDOC (`<" --body "$(cat < + +## Files modified +- \`path/to/file.md\` + +--- +Automated fix for issue #${ISSUE_NUM}. +EOF +)" +``` + +## Step 7: Update progress comment + +Replace the progress comment with a completion summary: + +```bash +gh api repos/$REPO/issues/comments/$PROGRESS_COMMENT_ID \ + -X PATCH -f body="$(cat <<'EOF' +**Fix complete:** + +- [x] Apply fix to `path/to/file.md` +- [x] Run Vale and Dale +- [x] Create PR + +**Summary:** +- `path/to/file.md`: + +PR: # +EOF +)" +``` + +## Failure handling + +If you encounter an error mid-execution (Vale not available, edit conflict, push failure): + +1. Post a comment on the issue explaining what went wrong and what was attempted +2. If a branch was created, note the branch name so a team member can continue manually +3. Do not leave the issue in a confusing state — always communicate what happened + +## Behavioral notes + +- **Confident = act, uncertain = ask.** If you can map a URL or find an exact file match, proceed. If the request is ambiguous, ask a clarifying question and stop. +- **Fix what's reported, nothing else.** If the issue says "fix the broken link in step 3," fix that link — don't also rewrite the sentence for clarity or fix grammar in other paragraphs. +- **If a fix would change the author's meaning**, skip it and note it in your summary. Ask how they'd like to handle it. +- **Each invocation is stateless.** You won't remember previous runs. Always read the full issue body and all comments for context. +- **Missing images cannot be fixed.** Note this in a comment and let a team member handle it. diff --git a/.github/workflows/claude-issue-labeler.yml b/.github/workflows/claude-issue-labeler.yml index f512423541..a36b24b347 100644 --- a/.github/workflows/claude-issue-labeler.yml +++ b/.github/workflows/claude-issue-labeler.yml @@ -97,3 +97,104 @@ jobs: Note: The skill should preserve template-assigned labels and add complementary labels. Issue body may have been modified by previous steps. claude_args: '--allowedTools "Bash(gh:*),Skill(assign-label)"' + + content-fix: + needs: process-issue + if: >- + github.event_name == 'issues' && + github.event.action == 'opened' && + contains(join(github.event.issue.labels.*.name, ','), 'documentation') && + contains(join(github.event.issue.labels.*.name, ','), 'fix') + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + issues: write + id-token: write + + steps: + - name: Check if issue is still open + id: check-state + run: | + STATE=$(gh issue view ${{ github.event.issue.number }} --repo ${{ github.repository }} --json state --jq .state) + echo "issue_state=$STATE" >> "$GITHUB_OUTPUT" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Checkout repository + if: steps.check-state.outputs.issue_state == 'OPEN' + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Run content-fix skill + if: steps.check-state.outputs.issue_state == 'OPEN' + uses: anthropics/claude-code-action@v1 + env: + REPO: ${{ github.repository }} + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + github_token: ${{ secrets.GITHUB_TOKEN }} + show_full_output: true + prompt: | + /content-fix ${{ github.event.issue.number }} + + Context: + - REPO: ${{ github.repository }} + - ISSUE_NUMBER: ${{ github.event.issue.number }} + - ISSUE_TITLE: ${{ github.event.issue.title }} + claude_args: '--allowedTools "Bash(gh:*),Bash(git:*),Bash(vale:*),Read,Write,Edit,Glob,Grep,Skill(content-fix),Skill(dale)"' + + content-fix-followup: + needs: process-issue + if: >- + github.event_name == 'issue_comment' && + !github.event.issue.pull_request && + contains(github.event.comment.body, '@claude') && + !startsWith(github.event.comment.user.login, 'github-actions') && + contains(join(github.event.issue.labels.*.name, ','), 'documentation') && + contains(join(github.event.issue.labels.*.name, ','), 'fix') + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + issues: write + id-token: write + + steps: + - name: Check if issue is still open + id: check-state + run: | + STATE=$(gh issue view ${{ github.event.issue.number }} --repo ${{ github.repository }} --json state --jq .state) + echo "issue_state=$STATE" >> "$GITHUB_OUTPUT" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Checkout repository + if: steps.check-state.outputs.issue_state == 'OPEN' + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Run content-fix skill + if: steps.check-state.outputs.issue_state == 'OPEN' + uses: anthropics/claude-code-action@v1 + env: + REPO: ${{ github.repository }} + COMMENT_BODY: ${{ github.event.comment.body }} + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + github_token: ${{ secrets.GITHUB_TOKEN }} + show_full_output: true + prompt: | + /content-fix ${{ github.event.issue.number }} + + Context: + - REPO: ${{ github.repository }} + - ISSUE_NUMBER: ${{ github.event.issue.number }} + - ISSUE_TITLE: ${{ github.event.issue.title }} + - TRIGGERING_COMMENT: $COMMENT_BODY + + This is a follow-up invocation. A user replied with @claude on a content_fix issue. + Read the full issue body AND all comments to understand the complete context. + claude_args: '--allowedTools "Bash(gh:*),Bash(git:*),Bash(vale:*),Read,Write,Edit,Glob,Grep,Skill(content-fix),Skill(dale)"' diff --git a/CLAUDE.md b/CLAUDE.md index 2f840c6b61..e20a9d371b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -73,7 +73,7 @@ PRs target `dev`. Never commit directly to `dev` or `main`. The `sync-dev-to-mai | `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 | +| `claude-issue-labeler.yml` | Issues opened/edited | Security screening, CoC check, auto-labeling, content fix automation | | `sync-dev-to-main.yml` | Daily 8 AM PST | Auto-merge dev to main | | `reindex-algolia.yml` | After main deploy | Refresh search index | @@ -90,6 +90,7 @@ When a user asks for help with documentation, always use the appropriate tool: | `/dale` | Skill | Custom linter for Netwrix-specific writing patterns | | `/doc-help` | Skill | Interactive writing assistant (terminal sessions) | | `/doc-pr` | Skill | Automated PR review (Dale + editorial) | +| `/content-fix` | Skill | Autonomous issue-to-PR fixer for content_fix issues | | `/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 |