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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.DS_Store
.env
.cache/
# Claude Code runtime state (ignored by default)
.claude/*
Expand Down
2 changes: 1 addition & 1 deletion plugins/docs-tools/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "docs-tools",
"version": "0.0.63",
"version": "0.0.64",
"description": "Documentation review, writing, and workflow tools for Red Hat AsciiDoc and Markdown documentation.",
"author": {
"name": "Red Hat Documentation Team",
Expand Down
10 changes: 7 additions & 3 deletions plugins/docs-tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@

- Install [software dependencies](https://redhat-documentation.github.io/redhat-docs-agent-tools/install/#software-dependencies)

- Create an `~/.env` file with your tokens:
- Create an `.env` file with your tokens. You can use either location:

- **`~/.env`** — global defaults, shared across all projects
- **`.env`** in the project root — project-specific overrides (takes precedence over `~/.env`)

```bash
JIRA_API_TOKEN=your_jira_api_token
# JIRA_AUTH_TOKEN is also accepted as a backward-compatible alias
# Required for Atlassian Cloud authentication
JIRA_EMAIL=you@redhat.com
# Optional: defaults to https://redhat.atlassian.net if not set
Expand All @@ -23,8 +27,8 @@
# Required scope: "api"
GITLAB_TOKEN=your_gitlab_pat
```
- Add the following to the end of your `~/.bashrc` (Linux) or `~/.zshrc` (macOS):

All scripts load both files automatically (global first, then local overrides). You can also add the following to `~/.bashrc` (Linux) or `~/.zshrc` (macOS) to export them into your shell:

```bash
if [ -f ~/.env ]; then
Expand Down
4 changes: 2 additions & 2 deletions plugins/docs-tools/agents/docs-planner.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ If either file cannot be read, **STOP** and report the error. Do not proceed fro

**You MUST successfully read the requirements input file before proceeding.** If the input file is missing or empty, STOP and report the error.

If access to JIRA or Git is needed for supplemental research and fails, **STOP IMMEDIATELY**, report the exact error, and instruct the user to check their credentials in `~/.env`. Never guess or infer content.
If access to JIRA or Git is needed for supplemental research and fails, **STOP IMMEDIATELY**, report the exact error, and instruct the user to check their credentials in `.env` or `~/.env`. Never guess or infer content.

**Do not** prepend `source ~/.env` to bash commands — all Python scripts load `~/.env` automatically.
**Do not** prepend `source ~/.env` to bash commands — all Python scripts load `.env` files automatically.

## When invoked

Expand Down
4 changes: 2 additions & 2 deletions plugins/docs-tools/agents/docs-writer.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ Before writing any documentation:

### JIRA/Git access failures during writing

If access to JIRA or Git fails during writing, **STOP IMMEDIATELY**, report the exact error, and instruct the user to check their credentials in `~/.env`. Never guess or infer content.
If access to JIRA or Git fails during writing, **STOP IMMEDIATELY**, report the exact error, and instruct the user to check their credentials in `.env` or `~/.env`. Never guess or infer content.

**Do not** prepend `source ~/.env` to bash commands — all Python scripts load `~/.env` automatically.
**Do not** prepend `source ~/.env` to bash commands — all Python scripts load `.env` files automatically.

## CRITICAL: Mandatory reference loading

Expand Down
4 changes: 2 additions & 2 deletions plugins/docs-tools/agents/requirements-analyst.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(git rev-parse --show-toplevel

Before fetching, inspect the requirement's `sources` list. Only attempt access to systems that are actually listed (JIRA for `type: "jira"`, Git for `type: "pr"`, etc.). If a listed source fails access, **STOP IMMEDIATELY** and return an error result (see output format). Do not hard-fail for systems that are not in the sources list.

**Do not** prepend `source ~/.env` to bash commands — all Python scripts load `~/.env` automatically.
**Do not** prepend `source ~/.env` to bash commands — all Python scripts load `.env` files automatically.

**Note:** The jira-reader script requires `jira` and `ratelimit` Python packages. If not installed: `python3 -m pip install jira ratelimit`

Expand Down Expand Up @@ -196,7 +196,7 @@ python3 ${CLAUDE_PLUGIN_ROOT}/skills/git-pr-reader/scripts/git_pr_reader.py file
python3 ${CLAUDE_PLUGIN_ROOT}/skills/git-pr-reader/scripts/git_pr_reader.py diff <pr-url>
```

Requires `GITHUB_TOKEN` (GitHub) or `GITLAB_TOKEN` (GitLab) in `~/.env`.
Requires `GITHUB_TOKEN` (GitHub) or `GITLAB_TOKEN` (GitLab) in `.env` or `~/.env`.

### Reading Red Hat documentation

Expand Down
2 changes: 1 addition & 1 deletion plugins/docs-tools/agents/requirements-discoverer.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ If JIRA access fails, **STOP IMMEDIATELY**, report the exact error in your JSON

Git access (for PR/MR details) is only required when PR/MR URLs are present — either provided manually or auto-discovered from the JIRA graph. If no PR/MR URLs exist, skip PR listing entirely. If a specific PR/MR URL fails to fetch, log it in the `errors` array but continue discovery from other sources.

**Do not** prepend `source ~/.env` to bash commands — all Python scripts load `~/.env` automatically.
**Do not** prepend `source ~/.env` to bash commands — all Python scripts load `.env` files automatically.

**Note:** The jira-reader script requires `jira` and `ratelimit` Python packages. If these are not installed, you will see `ModuleNotFoundError`. Run: `python3 -m pip install jira ratelimit`

Expand Down
4 changes: 2 additions & 2 deletions plugins/docs-tools/agents/technical-reviewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ You are not a style reviewer. You do not flag grammar, formatting, or style guid

## CRITICAL: Access failure procedure

If access to JIRA or Git fails during technical review, **STOP IMMEDIATELY**, report the exact error, and instruct the user to check their credentials in `~/.env`. Do not guess or infer technical details.
If access to JIRA or Git fails during technical review, **STOP IMMEDIATELY**, report the exact error, and instruct the user to check their credentials in `.env` or `~/.env`. Do not guess or infer technical details.

**Do not** prepend `source ~/.env` to bash commands — all Python scripts load `~/.env` automatically.
**Do not** prepend `source ~/.env` to bash commands — all Python scripts load `.env` files automatically.

## Your reviewer persona

Expand Down
2 changes: 1 addition & 1 deletion plugins/docs-tools/skills/docs-orchestrator/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Install the workflow completion Stop hook (safe to re-run, skips if already inst
bash ${CLAUDE_SKILL_DIR}/scripts/setup-hooks.sh
```

**Do not** source `~/.env` or check for tokens/CLIs here — Python scripts (`jira_reader.py`, `resolve_source.py`, etc.) load `~/.env` and validate prerequisites themselves, producing clear errors on failure.
**Do not** source `.env` files or check for tokens/CLIs here — Python scripts (`jira_reader.py`, `resolve_source.py`, etc.) load `.env` files and validate prerequisites themselves, producing clear errors on failure.

## Parse arguments

Expand Down
2 changes: 1 addition & 1 deletion plugins/docs-tools/skills/docs-review-style/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@ If any changes were applied, remind the user:
- Always use `git_pr_reader.py extract` for deterministic line numbers — never estimate or guess
- Use Bash with heredoc/cat for writing /tmp files (not the Write tool)
- Cite the specific style guide rule or review skill for each issue
- Comments are posted under YOUR username using tokens from `~/.env`
- Comments are posted under YOUR username using tokens from `.env` files
- For .adoc files, modular docs compliance uses `docs-review-modular-docs`
- Release notes skills only apply to .adoc files that appear to be release notes
- Vale linting requires Vale to be installed and configured
2 changes: 1 addition & 1 deletion plugins/docs-tools/skills/docs-review-technical/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,7 @@ Entities found in API surface but not referenced in reviewed documentation:
- Always use `git_pr_reader.py extract` for deterministic line numbers — never estimate or guess
- Use Bash with heredoc/cat for writing /tmp files (not the Write tool)
- Include source code evidence in each issue's `reason` field
- Comments are posted under YOUR username using tokens from `~/.env`
- Comments are posted under YOUR username using tokens from `.env` files
- `scripts/extract_refs.py` extracts technical references from doc files (commands, APIs, configs, file paths)
- Code-finder wrappers live in `${CLAUDE_PLUGIN_ROOT}/skills/code-evidence/scripts/`:
- `grounded_review.py` — validates doc claims against code (verdicts: supported/unsupported/partially_supported/no_evidence_found)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Unlike other step skills, this skill does **not** dispatch an agent. It runs `sc

## Environment

Requires `JIRA_API_TOKEN` and `JIRA_EMAIL` in the environment (typically sourced from `~/.env`).
Requires `JIRA_API_TOKEN` (or the backward-compatible alias `JIRA_AUTH_TOKEN`) and `JIRA_EMAIL` in the environment. `create-jira-ticket.sh` sources `~/.env` then `<project-root>/.env`, where the project root is resolved from the `PLAN_FILE` location.

## Execution

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,32 @@ PLAN_FILE="${3:?Missing PLAN_FILE argument}"

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"

# Source ~/.env for credential mapping
source ~/.env 2>/dev/null || true
# Load local overrides first, then global defaults (resolve .env from project root)
# Safe key/value parser: only reads KEY=VALUE lines, skips shell commands
_safe_load_env() {
local file="$1"
[[ -f "$file" ]] || return 0
while IFS= read -r line || [[ -n "$line" ]]; do
[[ "$line" =~ ^[[:space:]]*# ]] && continue
[[ "$line" =~ ^[[:space:]]*$ ]] && continue
[[ "$line" =~ ^[[:space:]]*([a-zA-Z_][a-zA-Z0-9_]*)[[:space:]]*=(.*) ]] || continue
local key="${BASH_REMATCH[1]}"
local value="${BASH_REMATCH[2]}"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
if [[ "$value" =~ ^\"(.*)\"$ ]] || [[ "$value" =~ ^\'(.*)\'$ ]]; then
value="${BASH_REMATCH[1]}"
fi
if [[ -z "${!key+x}" ]]; then
export "$key=$value"
fi
done < "$file"
}
_project_root="$(cd "$(dirname "$PLAN_FILE")" 2>/dev/null && git rev-parse --show-toplevel 2>/dev/null || true)"
if [[ -n "$_project_root" ]]; then
_safe_load_env "$_project_root/.env"
fi
Comment thread
aireilly marked this conversation as resolved.
_safe_load_env ~/.env
# Fallback: accept JIRA_AUTH_TOKEN for backward compatibility
: "${JIRA_API_TOKEN:=${JIRA_AUTH_TOKEN:-}}"
JIRA_URL="${JIRA_URL:-https://redhat.atlassian.net}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Gate skill for automated docs-orchestrator runs. Checks JIRA for new tickets mat

## Environment

Requires `JIRA_API_TOKEN` and `JIRA_EMAIL` in the environment (typically sourced from `~/.env`). Both are validated before any API calls.
Requires `JIRA_API_TOKEN` (or the backward-compatible alias `JIRA_AUTH_TOKEN`) and `JIRA_EMAIL` in the environment. `jira-ready-check.sh` loads `~/.env` then `<project-root>/.env` using a safe key/value parser (no shell execution). Both variables are validated before any API calls.

## Execution

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,45 @@ if [[ -z "$JQL" ]]; then
exit 1
fi

# --- Validate environment ---
if [[ -z "${JIRA_API_TOKEN:-}" ]]; then
# Try sourcing ~/.env
set -a; source ~/.env 2>/dev/null || true; set +a
# --- Load environment ---
# Safe key/value parser: only reads KEY=VALUE lines, skips shell commands
_safe_load_env() {
local file="$1"
[[ -f "$file" ]] || return 0
while IFS= read -r line || [[ -n "$line" ]]; do
[[ "$line" =~ ^[[:space:]]*# ]] && continue
[[ "$line" =~ ^[[:space:]]*$ ]] && continue
[[ "$line" =~ ^[[:space:]]*([a-zA-Z_][a-zA-Z0-9_]*)[[:space:]]*=(.*) ]] || continue
local key="${BASH_REMATCH[1]}"
local value="${BASH_REMATCH[2]}"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
if [[ "$value" =~ ^\"(.*)\"$ ]] || [[ "$value" =~ ^\'(.*)\'$ ]]; then
value="${BASH_REMATCH[1]}"
fi
if [[ -z "${!key+x}" ]]; then
export "$key=$value"
fi
done < "$file"
}
_project_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
if [[ -z "$_project_root" ]]; then
_project_root="$(cd "$SCRIPT_DIR" 2>/dev/null && git rev-parse --show-toplevel 2>/dev/null || true)"
fi
if [[ -n "$_project_root" ]]; then
_safe_load_env "$_project_root/.env"
fi
_safe_load_env ~/.env
# Fallback: accept JIRA_AUTH_TOKEN for backward compatibility
: "${JIRA_API_TOKEN:=${JIRA_AUTH_TOKEN:-}}"

if [[ -z "${JIRA_API_TOKEN:-}" ]]; then
echo '{"error": "JIRA_API_TOKEN is not set. Add it to ~/.env."}'
echo '{"error": "JIRA_API_TOKEN is not set. Add it to .env or ~/.env."}'
exit 1
fi

if [[ -z "${JIRA_EMAIL:-}" ]]; then
echo '{"error": "JIRA_EMAIL is not set. Add it to ~/.env."}'
echo '{"error": "JIRA_EMAIL is not set. Add it to .env or ~/.env."}'
exit 1
fi

Expand Down
4 changes: 2 additions & 2 deletions plugins/docs-tools/skills/git-pr-reader/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,14 @@ python3 ${CLAUDE_SKILL_DIR}/scripts/git_pr_reader.py detect --json

### Authentication

Set in `~/.env` (see docs-tools README for setup):
Set in `~/.env` (global) or `.env` in the project root (local override). See docs-tools README for setup:

```bash
GITHUB_TOKEN=your-github-pat # required scope: "repo" for private, "public_repo" for public
GITLAB_TOKEN=your-gitlab-pat # required scope: "api"
```

The script loads `~/.env` automatically — do **not** prepend `source ~/.env` to bash commands.
The script loads `.env` files automatically — do **not** prepend `source ~/.env` to bash commands.

### Python Library Usage

Expand Down
32 changes: 21 additions & 11 deletions plugins/docs-tools/skills/git-pr-reader/scripts/git_pr_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
python git_pr_reader.py detect

Authentication:
Requires tokens in ~/.env:
Requires tokens in .env or ~/.env:
- GitHub: GITHUB_TOKEN environment variable
- GitLab: GITLAB_TOKEN environment variable
"""
Expand Down Expand Up @@ -83,15 +83,25 @@


def load_env_file() -> None:
"""Load environment variables from ~/.env file."""
env_file = os.path.expanduser("~/.env")
if os.path.exists(env_file):
with open(env_file) as f:
for line in f:
line = line.strip()
if line and not line.startswith("#") and "=" in line:
key, value = line.split("=", 1)
os.environ.setdefault(key.strip(), value.strip())
"""Load environment variables from .env files.

Loads ./.env first (local settings), then ~/.env (global defaults).
Pre-existing environment variables are never overwritten.
Surrounding quotes on values are stripped.
"""
for env_path in [".env", os.path.expanduser("~/.env")]:
if os.path.exists(env_path):
with open(env_path) as f:
for line in f:
line = line.strip()
if line and not line.startswith("#") and "=" in line:
key, value = line.split("=", 1)
value = value.strip()
if (value.startswith('"') and value.endswith('"')) or (
value.startswith("'") and value.endswith("'")
):
value = value[1:-1]
os.environ.setdefault(key.strip(), value)


def color_print(prefix: str, message: str) -> None:
Expand Down Expand Up @@ -1612,7 +1622,7 @@ def cmd_detect(args) -> int:
# Try GitLab via urllib.request
gitlab_token = os.environ.get("GITLAB_TOKEN")
if not gitlab_token:
print("Error: GITLAB_TOKEN not set in ~/.env")
print("Error: GITLAB_TOKEN not set in .env or ~/.env")
return 1

for remote_name, remote_url in remotes.items():
Expand Down
4 changes: 2 additions & 2 deletions plugins/docs-tools/skills/jira-reader/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ The skill uses a Python script that connects to JIRA using an authentication tok

### Environment Variables Required

Set in `~/.env` (see docs-tools README for setup):
Set in `~/.env` (global) or `.env` in the project root (local override). See docs-tools README for setup:

```bash
JIRA_API_TOKEN=your-jira-api-token
JIRA_EMAIL=you@redhat.com # required for Atlassian Cloud
JIRA_URL=https://redhat.atlassian.net # optional, defaults to redhat.atlassian.net
```

The script loads `~/.env` automatically — do **not** prepend `source ~/.env` to bash commands.
The script loads `.env` files automatically — do **not** prepend `source ~/.env` to bash commands.

### Examples

Expand Down
34 changes: 23 additions & 11 deletions plugins/docs-tools/skills/jira-reader/scripts/jira_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,15 +125,25 @@ def adf_to_text(node):


def load_env_file():
"""Load environment variables from ~/.env file."""
env_file = os.path.expanduser("~/.env")
if os.path.exists(env_file):
with open(env_file) as f:
for line in f:
line = line.strip()
if line and not line.startswith("#") and "=" in line:
key, value = line.split("=", 1)
os.environ.setdefault(key.strip(), value.strip())
"""Load environment variables from .env files.

Loads ./.env first (local settings), then ~/.env (global defaults).
Pre-existing environment variables are never overwritten.
Surrounding quotes on values are stripped.
"""
for env_path in [".env", os.path.expanduser("~/.env")]:
if os.path.exists(env_path):
with open(env_path) as f:
for line in f:
line = line.strip()
if line and not line.startswith("#") and "=" in line:
key, value = line.split("=", 1)
value = value.strip()
if (value.startswith('"') and value.endswith('"')) or (
value.startswith("'") and value.endswith("'")
):
value = value[1:-1]
os.environ.setdefault(key.strip(), value)


class JiraReader:
Expand All @@ -145,7 +155,9 @@ def __init__(self, server=None):

token = os.environ.get("JIRA_API_TOKEN") or os.environ.get("JIRA_AUTH_TOKEN")
if not token:
raise ValueError("JIRA_API_TOKEN environment variable not set. Add it to ~/.env")
raise ValueError(
"JIRA_API_TOKEN (or JIRA_AUTH_TOKEN) not set. Add it to .env or ~/.env"
)

server = server or os.environ.get("JIRA_URL", "https://redhat.atlassian.net")

Expand All @@ -155,7 +167,7 @@ def __init__(self, server=None):
if not email:
raise ValueError(
"JIRA_EMAIL environment variable not set. "
"Required for Atlassian Cloud. Add it to ~/.env"
"Required for Atlassian Cloud. Add it to .env or ~/.env"
)
self.jira = JIRA(server=server, basic_auth=(email, token), options=options)
else:
Expand Down
4 changes: 2 additions & 2 deletions plugins/docs-tools/skills/jira-writer/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ The skill uses a Python script that connects to JIRA using an authentication tok

### Environment Variables Required

Set in `~/.env` (see docs-tools README for setup):
Set in `~/.env` (global) or `.env` in the project root (local override). See docs-tools README for setup:

```bash
JIRA_API_TOKEN=your-jira-api-token
JIRA_EMAIL=you@redhat.com # required for Atlassian Cloud
JIRA_URL=https://redhat.atlassian.net # optional, defaults to redhat.atlassian.net
```

The script loads `~/.env` automatically — do **not** prepend `source ~/.env` to bash commands.
The script loads `.env` files automatically — do **not** prepend `source ~/.env` to bash commands.

### Examples

Expand Down
Loading
Loading