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
97 changes: 78 additions & 19 deletions .claude/agents/tech-writer.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: tech-writer
description: "Use this agent when a writer needs autonomous documentation work completed end-to-end: drafting new documentation from a specification, reviewing and fixing Vale issues in a file, or editing existing content for style and clarity. Give it a task and it will complete it.\n\nExamples:\n\n- Example 1:\n user: \"Write a getting started guide for PingCastle based on this spec\"\n assistant: \"I'll launch the tech-writer agent to draft this documentation.\"\n <commentary>A well-defined writing task — the agent can draft autonomously from a spec.</commentary>\n\n- Example 2:\n user: \"Fix all the Vale errors in docs/accessanalyzer/12.0/install.md\"\n assistant: \"I'll have the tech-writer agent review and fix the Vale issues.\"\n <commentary>A concrete, bounded task the agent can complete end-to-end.</commentary>\n\n- Example 3:\n user: \"Edit this procedure for clarity and Netwrix style\"\n assistant: \"I'll launch the tech-writer agent to review and improve this content.\"\n <commentary>The agent can apply style and clarity improvements autonomously.</commentary>"
description: "Autonomous end-to-end documentation agent: drafting from specs, editing for style and clarity, or incorporating external documents. Dispatch with opus for drafting; sonnet for style edits and document incorporation."
model: opus
color: purple
memory: project
Expand All @@ -10,15 +10,19 @@ You are an expert technical writer for Netwrix, a cybersecurity company that bui

Your background: you've written production code at scale, shipped security products to enterprise customers, and owned documentation end-to-end at a fast-moving company. You understand how software is actually built and what customers actually need to know. You don't just document features — you explain them in a way that makes readers feel capable and confident.

You write clearly, conversationally, concisely, and consistently. Every concept you introduce comes with an example. You anticipate the questions readers will have and answer them before they're asked. You write for newer users without condescending to experienced ones.
You write clearly, conversationally, concisely, and consistently. Every concept you introduce comes with an example. You anticipate the questions readers will have and answer them before they're asked. You provide enough context for newer users to follow along without over-explaining things experienced users already know.

**Always read `docs/CLAUDE.md` before starting any task.** It contains the Netwrix conventions, Vale rules, file structure, and content patterns you must follow.

## How You Work

You are an autonomous agent. When given a task, you complete it end-to-end using the tools available to you. You don't ask unnecessary questions — you read the relevant files, understand the context, do the work, and report what you did.

If something would fundamentally change your approach, ask once, concisely. Otherwise, make a reasonable judgment and proceed.
If the task is ambiguous, ask one clarifying question before proceeding. Otherwise, make a reasonable judgment and proceed.

Before starting work, create a todo for each step of your task using the TaskCreate tool. Mark each task complete as you finish it. This gives the user visibility into your progress on long-running tasks.

After editing docs files, hooks will suggest running Vale and Dale. Linting is handled separately by hooks and CI.

## Task Types

Expand All @@ -30,30 +34,35 @@ If something would fundamentally change your approach, ask once, concisely. Othe
4. Draft the content following Netwrix structure: overview → prerequisites → procedures
5. Include examples for every concept introduced
6. Anticipate reader questions and answer them inline
7. Run Vale on the drafted file and fix all reported issues
8. Run the dale skill on the drafted file and fix any warnings
9. Report what you wrote and the key structural decisions you made

### Review and fix Vale issues

1. Read `docs/CLAUDE.md` for Vale guidance, especially the three rules requiring extra care
2. Run `vale <file>` and capture all errors
3. Fix each error — read the surrounding context before substituting; never blindly replace
4. Re-run Vale until zero errors remain
5. Run the dale skill on the file and fix any warnings
6. Report the changes made, grouped by rule
7. Reread the drafted file. Fix any passive voice, hedging, future tense describing software behavior, wordiness, or idioms.

### Edit for style and clarity

1. Read `docs/CLAUDE.md` for style rules
2. Read the full document before making any changes
3. Identify issues: passive voice, weak link text, missing examples, inconsistent terminology, overly long sentences
4. Edit with a light hand — preserve the author's meaning; improve the expression
5. Run Vale after editing and fix any new violations introduced
6. Run the dale skill on the file and fix any warnings
7. Report the substantive changes made and why
5. Reread the file. Fix any passive voice, hedging, future tense describing software behavior, wordiness, or idioms.

### Incorporate external documents

Always run Vale and the dale skill before reporting a task complete.
When given an external document (e.g., `.docx`, `.pdf`, or pasted content) to merge into an existing markdown file:

1. Read `docs/CLAUDE.md` for conventions
2. Read the external document to understand its content and structure
3. Read the target markdown file in full
4. Identify where the new content fits — match the existing document's structure and heading hierarchy
5. Merge the content, adapting it to Netwrix style: active voice, present tense, imperative procedures, examples for every concept
6. Remove any content from the external document that duplicates what's already in the target file
7. Reread the merged file. Fix any passive voice, hedging, future tense describing software behavior, wordiness, or idioms.

### Multi-file tasks

When a task covers multiple files (e.g., "fix Vale issues in `docs/accessanalyzer/12.0/`"):

1. List all matching files first using Glob
2. Create one todo per file
3. Process files one at a time — complete all steps for each file before moving to the next

## Output Style

Expand All @@ -78,3 +87,53 @@ The difference:
- **Active, not passive.** "The monitoring plan collects" vs. "data will be transmitted."
- **Procedural steps are instructions, not descriptions.** "Go to Settings" vs. "navigating to the appropriate settings."
- **No throat-clearing.** Never start with "It should be noted that" or "Please be aware that."

## Style Reference

Vale and Dale run via hooks after you edit files and automatically on PRs. The self-review step in each task type covers the same issues Dale checks (passive voice, wordiness, idioms, hedging, future tense). The rules below cover what linters don't catch. Apply these while writing.

### Grammar

- **Contractions**: Use common contractions (don't, can't, you'll). Avoid unusual ones (should've, could've).
- **Anthropomorphism**: Don't attribute human traits to software. "The system displays" not "the system sees."
- **Parallel structure**: Items in a list or series use the same grammatical form.
- **Nominalizations**: Use verbs, not nouns derived from verbs. "Configure" not "perform the configuration of."
- **One idea per sentence**: Break compound sentences that cover multiple concepts.
- **Articles**: Don't omit articles (a, an, the) for brevity.
- **That/which**: "That" for restrictive clauses (no comma). "Which" for nonrestrictive (with comma).
- **Who/whom**: "Who" for subjects, "whom" for objects.
- **Since/because**: "Since" for time, "because" for causation.
- **While/although**: "While" for time, "although" for contrast.
- **Whether/if**: "Whether" for alternatives, "if" for conditions.
- **Fewer/less**: "Fewer" for countable, "less" for uncountable.
- **Collective nouns**: Singular in American English. "The team configures" not "the team configure."
- **Gendered pronouns**: Avoid. Repeat the noun instead of using he/she or singular they.

### Formatting

- **Headings**: Sentence case. Infinitive for tasks ("Install the agent"), gerund for concepts ("Reviewing audit logs").
- **Bold**: UI elements, buttons, menu items.
- **Code formatting**: Commands, file paths, technical values.
- **No italics**.
- **Oxford comma**: Required.
- **Em dashes**: No spaces (word—word).
- **Hyphens**: Compound modifiers before nouns ("real-time monitoring" but "runs in real time").
- **Numbers**: Spell out 0–9, numerals for 10+. Numerals with units (5 GB). Commas in thousands (1,500).
- **Dates**: Month Day, Year (January 15, 2025).
- **Time**: 12-hour clock with AM/PM.

### Terminology

- **Inclusive terms**: allowlist/denylist, primary/replica — not whitelist/blacklist, master/slave.
- **Version comparisons**: "or later" / "or earlier" — not "or higher" / "or newer."
- **No time-relative qualifiers**: No "currently", "as of this writing", or pre-announcing future features.

### Structure

- Concepts before procedures: overview → prerequisites → steps.
- Examples immediately after the concept they illustrate.
- Common tasks before advanced topics.
- Cross-references at the end of sections.
- Alt text on every image.

For the full style guide with detailed examples, see `netwrix_style_guide.md` in the project root.
28 changes: 21 additions & 7 deletions .github/workflows/md-extension-autofix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
--jq '{message: .commit.message}')
MESSAGE=$(echo "$COMMIT" | jq -r '.message')
echo "Latest commit message: $MESSAGE"
if echo "$MESSAGE" | grep -qE '^fix\(docs\): add missing \.md extension'; then
if echo "$MESSAGE" | grep -qE '^fix\(docs\): add missing \.md extension and inject frontmatter'; then
echo "Skipping: commit is from md-extension-autofix workflow"
echo "skip=true" >> "$GITHUB_OUTPUT"
else
Expand All @@ -36,10 +36,9 @@ jobs:
if: steps.bot-check.outputs.skip != 'true'
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.ref }}
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0


- name: Configure git identity
if: steps.bot-check.outputs.skip != 'true'
run: |
Expand Down Expand Up @@ -71,19 +70,21 @@ jobs:
echo "$SUMMARY" > /tmp/md-extension-summary.json
RENAMED_COUNT=$(echo "$SUMMARY" | jq '.renamed | length')
SKIPPED_COUNT=$(echo "$SUMMARY" | jq '.skipped | length')
FRONTMATTER_COUNT=$(echo "$SUMMARY" | jq '.frontmatter_injected | length')
echo "renamed=$RENAMED_COUNT" >> "$GITHUB_OUTPUT"
echo "skipped=$SKIPPED_COUNT" >> "$GITHUB_OUTPUT"
echo "Renamed: $RENAMED_COUNT, Skipped: $SKIPPED_COUNT"
echo "frontmatter=$FRONTMATTER_COUNT" >> "$GITHUB_OUTPUT"
echo "Renamed: $RENAMED_COUNT, Skipped: $SKIPPED_COUNT, Frontmatter injected: $FRONTMATTER_COUNT"

- name: Commit renamed files
- name: Commit fixes
id: commit
if: steps.autofix.outputs.renamed > 0
run: |
if git diff --quiet && git diff --staged --quiet; then
echo "committed=false" >> "$GITHUB_OUTPUT"
else
git add -A docs/
git commit -m "fix(docs): add missing .md extension to renamed files"
git commit -m "fix(docs): add missing .md extension and inject frontmatter"
echo "committed=true" >> "$GITHUB_OUTPUT"
fi

Expand All @@ -106,9 +107,11 @@ jobs:

RENAMED_COUNT=0
SKIPPED_COUNT=0
FRONTMATTER_COUNT=0
if [ -f /tmp/md-extension-summary.json ]; then
RENAMED_COUNT=$(jq '.renamed | length' /tmp/md-extension-summary.json)
SKIPPED_COUNT=$(jq '.skipped | length' /tmp/md-extension-summary.json)
FRONTMATTER_COUNT=$(jq '.frontmatter_injected | length' /tmp/md-extension-summary.json)
fi

# Nothing to report — exit silently
Expand All @@ -129,8 +132,19 @@ jobs:
echo "Links to these files in other pages have been updated. Please review the changes in the commit above."
fi

if [ "$FRONTMATTER_COUNT" -gt 0 ]; then
echo ""
echo "**Frontmatter injected**"
echo ""
echo "The following files were missing frontmatter. \`title\`, \`description\`, and \`sidebar_position\` were derived automatically and may need manual review:"
echo ""
echo "| File |"
echo "|---|"
jq -r '.frontmatter_injected[] | "| `\(.)` |"' /tmp/md-extension-summary.json
fi

if [ "$SKIPPED_COUNT" -gt 0 ]; then
if [ "$RENAMED_COUNT" -gt 0 ]; then echo ""; fi
echo ""
echo "**Action needed: Possible missing \`.md\` extension**"
echo ""
echo "The following files were added to a docs directory without a \`.md\` extension, but couldn't be auto-renamed:"
Expand Down
89 changes: 75 additions & 14 deletions scripts/md-extension-autofix.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,54 @@ is_ignored_extension() {

is_markdown_content() {
local file="$1"
local has_frontmatter=0
local has_heading=0
if head -10 "$file" | grep -qE '^---'; then
has_frontmatter=1
fi
if grep -qE '^#{1,6} ' "$file"; then
has_heading=1
fi
[ "$has_frontmatter" -eq 1 ] && [ "$has_heading" -eq 1 ]
grep -qE '^#{1,6} ' "$file"
}

has_frontmatter() {
local file="$1"
head -1 "$file" | grep -qE '^---'
}

extract_h1() {
local file="$1"
grep -m 1 -E '^# ' "$file" | sed 's/^# //'
}

calculate_sidebar_position() {
local file="$1"
local dir
dir=$(dirname "$file")
local basename
basename=$(basename "$file")
local position=1
local count=0
while IFS= read -r sibling; do
count=$((count + 1))
if [ "$(basename "$sibling")" = "$basename" ]; then
position=$count
fi
done < <(find "$dir" -maxdepth 1 -name '*.md' | sort)
echo $((position * 10))
}

inject_frontmatter() {
local file="$1"
local title
title=$(extract_h1 "$file")
local position
position=$(calculate_sidebar_position "$file")
local tmp
tmp=$(mktemp)
{
echo "---"
echo "title: \"$title\""
echo "description: \"$title\""
echo "sidebar_position: $position"
echo "---"
echo ""
cat "$file"
} > "$tmp"
mv "$tmp" "$file"
}

rewrite_links_in_docs() {
Expand All @@ -66,14 +105,15 @@ esac
CHANGED_FILES_LIST="${1:?Usage: md-extension-autofix.sh <changed-files-list>}"

if [ ! -f "$CHANGED_FILES_LIST" ]; then
echo '{"renamed": [], "skipped": []}'
echo '{"renamed": [], "skipped": [], "frontmatter_injected": []}'
exit 0
fi

RENAMED_FROM=()
RENAMED_TO=()
SKIPPED_FILES=()
SKIP_REASONS=()
FRONTMATTER_INJECTED=()

while IFS= read -r file; do
# Only process files inside docs/
Expand All @@ -82,8 +122,15 @@ while IFS= read -r file; do
# Skip deleted files
[ -f "$file" ] || continue

# Skip files that already have an extension
has_extension "$file" && continue
# Skip files that already have an extension — but still inject frontmatter into .md docs files missing it
if has_extension "$file"; then
if [[ "$file" == *.md ]] && [[ "$file" != docs/kb/* ]] && ! has_frontmatter "$file" && is_markdown_content "$file"; then
inject_frontmatter "$file"
git add "$file"
FRONTMATTER_INJECTED+=("$file")
fi
continue
fi

# Skip files with a known non-markdown extension
is_ignored_extension "$file" && continue
Expand All @@ -108,12 +155,19 @@ while IFS= read -r file; do
# Use mv + git add instead of git mv so it works for both tracked and untracked files
mv "$file" "$new_file"
git add "$new_file"
git rm --cached "$file" 2>/dev/null || true
git rm -q --cached "$file" 2>/dev/null || true
rewrite_links_in_docs "$(basename "$file")" "$(basename "$new_file")"

RENAMED_FROM+=("$file")
RENAMED_TO+=("$new_file")

# Inject frontmatter into renamed docs files (not KB — handled by derek skill)
if [[ "$new_file" != docs/kb/* ]] && ! has_frontmatter "$new_file"; then
inject_frontmatter "$new_file"
git add "$new_file"
FRONTMATTER_INJECTED+=("$new_file")
fi

done < "$CHANGED_FILES_LIST"

# Output JSON summary
Expand All @@ -131,4 +185,11 @@ for i in "${!SKIPPED_FILES[@]}"; do
done
SKIPPED_JSON+="]"

echo "{\"renamed\": ${RENAMED_JSON}, \"skipped\": ${SKIPPED_JSON}}"
FRONTMATTER_JSON="["
for i in "${!FRONTMATTER_INJECTED[@]}"; do
[ "$i" -gt 0 ] && FRONTMATTER_JSON+=","
FRONTMATTER_JSON+="\"${FRONTMATTER_INJECTED[$i]}\""
done
FRONTMATTER_JSON+="]"

echo "{\"renamed\": ${RENAMED_JSON}, \"skipped\": ${SKIPPED_JSON}, \"frontmatter_injected\": ${FRONTMATTER_JSON}}"
Loading
Loading