From 39fd9711939b7160c175c2a89f67324719ca8204 Mon Sep 17 00:00:00 2001 From: Aidan Reilly Date: Sat, 2 May 2026 13:09:29 +0100 Subject: [PATCH 1/4] fix: support local .env in addition to ~/.env Harden .env loading across all docs-tools scripts: - Shell scripts: resolve .env from project root (via git rev-parse) instead of relying on CWD; use safe key/value parser instead of source to prevent arbitrary shell execution from .env files - Python scripts: use setdefault for both .env and ~/.env so pre-existing env vars are never overwritten; load .env before ~/.env so local settings take precedence; strip surrounding quotes from values - jira-ready-check.sh: source env unconditionally (not just when JIRA_API_TOKEN is empty) so JIRA_EMAIL is also loaded - Documentation: mention JIRA_AUTH_TOKEN backward-compatible alias and JIRA_EMAIL requirement in SKILL.md files - Error messages: mention both JIRA_API_TOKEN and JIRA_AUTH_TOKEN Co-Authored-By: Claude Opus 4.6 rh-pre-commit.version: 2.3.2 rh-pre-commit.check-secrets: ENABLED --- .gitignore | 1 + plugins/docs-tools/.claude-plugin/plugin.json | 2 +- plugins/docs-tools/README.md | 10 ++++-- plugins/docs-tools/agents/docs-planner.md | 4 +-- plugins/docs-tools/agents/docs-writer.md | 4 +-- .../docs-tools/agents/requirements-analyst.md | 4 +-- .../agents/requirements-discoverer.md | 2 +- .../docs-tools/agents/technical-reviewer.md | 4 +-- .../skills/docs-orchestrator/SKILL.md | 2 +- .../skills/docs-review-style/SKILL.md | 2 +- .../skills/docs-review-technical/SKILL.md | 2 +- .../skills/docs-workflow-create-jira/SKILL.md | 2 +- .../scripts/create-jira-ticket.sh | 28 +++++++++++++-- .../skills/docs-workflow-jira-ready/SKILL.md | 2 +- .../scripts/jira-ready-check.sh | 35 +++++++++++++++---- .../docs-tools/skills/git-pr-reader/SKILL.md | 4 +-- .../git-pr-reader/scripts/git_pr_reader.py | 33 ++++++++++------- .../docs-tools/skills/jira-reader/SKILL.md | 4 +-- .../skills/jira-reader/scripts/jira_reader.py | 35 +++++++++++++------ .../docs-tools/skills/jira-writer/SKILL.md | 4 +-- .../skills/jira-writer/scripts/jira_writer.py | 35 +++++++++++++------ 21 files changed, 153 insertions(+), 66 deletions(-) diff --git a/.gitignore b/.gitignore index a0a03556..ab43e056 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store +.env .cache/ # Claude Code runtime state (ignored by default) .claude/* diff --git a/plugins/docs-tools/.claude-plugin/plugin.json b/plugins/docs-tools/.claude-plugin/plugin.json index 5464925c..3ca2c03e 100644 --- a/plugins/docs-tools/.claude-plugin/plugin.json +++ b/plugins/docs-tools/.claude-plugin/plugin.json @@ -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", diff --git a/plugins/docs-tools/README.md b/plugins/docs-tools/README.md index f85635be..5424e599 100644 --- a/plugins/docs-tools/README.md +++ b/plugins/docs-tools/README.md @@ -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 @@ -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 diff --git a/plugins/docs-tools/agents/docs-planner.md b/plugins/docs-tools/agents/docs-planner.md index 6c8b67e6..a232ba89 100644 --- a/plugins/docs-tools/agents/docs-planner.md +++ b/plugins/docs-tools/agents/docs-planner.md @@ -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 diff --git a/plugins/docs-tools/agents/docs-writer.md b/plugins/docs-tools/agents/docs-writer.md index c41f48f1..564d04cb 100644 --- a/plugins/docs-tools/agents/docs-writer.md +++ b/plugins/docs-tools/agents/docs-writer.md @@ -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 diff --git a/plugins/docs-tools/agents/requirements-analyst.md b/plugins/docs-tools/agents/requirements-analyst.md index 7103e4d4..8e987a64 100644 --- a/plugins/docs-tools/agents/requirements-analyst.md +++ b/plugins/docs-tools/agents/requirements-analyst.md @@ -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` @@ -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 ``` -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 diff --git a/plugins/docs-tools/agents/requirements-discoverer.md b/plugins/docs-tools/agents/requirements-discoverer.md index 7eced1e0..ecaa84c3 100644 --- a/plugins/docs-tools/agents/requirements-discoverer.md +++ b/plugins/docs-tools/agents/requirements-discoverer.md @@ -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` diff --git a/plugins/docs-tools/agents/technical-reviewer.md b/plugins/docs-tools/agents/technical-reviewer.md index ab83820d..380f0b71 100644 --- a/plugins/docs-tools/agents/technical-reviewer.md +++ b/plugins/docs-tools/agents/technical-reviewer.md @@ -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 diff --git a/plugins/docs-tools/skills/docs-orchestrator/SKILL.md b/plugins/docs-tools/skills/docs-orchestrator/SKILL.md index 887ca9d1..610e62f9 100644 --- a/plugins/docs-tools/skills/docs-orchestrator/SKILL.md +++ b/plugins/docs-tools/skills/docs-orchestrator/SKILL.md @@ -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 diff --git a/plugins/docs-tools/skills/docs-review-style/SKILL.md b/plugins/docs-tools/skills/docs-review-style/SKILL.md index dbbec7f9..c2884daf 100644 --- a/plugins/docs-tools/skills/docs-review-style/SKILL.md +++ b/plugins/docs-tools/skills/docs-review-style/SKILL.md @@ -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 diff --git a/plugins/docs-tools/skills/docs-review-technical/SKILL.md b/plugins/docs-tools/skills/docs-review-technical/SKILL.md index 9ca3518c..90b7ba3d 100644 --- a/plugins/docs-tools/skills/docs-review-technical/SKILL.md +++ b/plugins/docs-tools/skills/docs-review-technical/SKILL.md @@ -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) diff --git a/plugins/docs-tools/skills/docs-workflow-create-jira/SKILL.md b/plugins/docs-tools/skills/docs-workflow-create-jira/SKILL.md index a97bb2bb..f9bc7522 100644 --- a/plugins/docs-tools/skills/docs-workflow-create-jira/SKILL.md +++ b/plugins/docs-tools/skills/docs-workflow-create-jira/SKILL.md @@ -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 `/.env`, where the project root is resolved from the `PLAN_FILE` location. ## Execution diff --git a/plugins/docs-tools/skills/docs-workflow-create-jira/scripts/create-jira-ticket.sh b/plugins/docs-tools/skills/docs-workflow-create-jira/scripts/create-jira-ticket.sh index f77772d6..f38ce033 100755 --- a/plugins/docs-tools/skills/docs-workflow-create-jira/scripts/create-jira-ticket.sh +++ b/plugins/docs-tools/skills/docs-workflow-create-jira/scripts/create-jira-ticket.sh @@ -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 global defaults, then local overrides (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" +} +_safe_load_env ~/.env +_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 # Fallback: accept JIRA_AUTH_TOKEN for backward compatibility : "${JIRA_API_TOKEN:=${JIRA_AUTH_TOKEN:-}}" JIRA_URL="${JIRA_URL:-https://redhat.atlassian.net}" diff --git a/plugins/docs-tools/skills/docs-workflow-jira-ready/SKILL.md b/plugins/docs-tools/skills/docs-workflow-jira-ready/SKILL.md index 87fe6300..ad11fa92 100644 --- a/plugins/docs-tools/skills/docs-workflow-jira-ready/SKILL.md +++ b/plugins/docs-tools/skills/docs-workflow-jira-ready/SKILL.md @@ -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 `/.env` using a safe key/value parser (no shell execution). Both variables are validated before any API calls. ## Execution diff --git a/plugins/docs-tools/skills/docs-workflow-jira-ready/scripts/jira-ready-check.sh b/plugins/docs-tools/skills/docs-workflow-jira-ready/scripts/jira-ready-check.sh index ec0a5894..8d810492 100755 --- a/plugins/docs-tools/skills/docs-workflow-jira-ready/scripts/jira-ready-check.sh +++ b/plugins/docs-tools/skills/docs-workflow-jira-ready/scripts/jira-ready-check.sh @@ -58,19 +58,42 @@ 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" +} +_safe_load_env ~/.env +_project_root="$(cd "$SCRIPT_DIR" 2>/dev/null && git rev-parse --show-toplevel 2>/dev/null || true)" +if [[ -n "$_project_root" ]]; then + _safe_load_env "$_project_root/.env" fi +# 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 diff --git a/plugins/docs-tools/skills/git-pr-reader/SKILL.md b/plugins/docs-tools/skills/git-pr-reader/SKILL.md index 933e332c..1ca5dacf 100644 --- a/plugins/docs-tools/skills/git-pr-reader/SKILL.md +++ b/plugins/docs-tools/skills/git-pr-reader/SKILL.md @@ -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 diff --git a/plugins/docs-tools/skills/git-pr-reader/scripts/git_pr_reader.py b/plugins/docs-tools/skills/git-pr-reader/scripts/git_pr_reader.py index 9d43a100..5f5e3cec 100755 --- a/plugins/docs-tools/skills/git-pr-reader/scripts/git_pr_reader.py +++ b/plugins/docs-tools/skills/git-pr-reader/scripts/git_pr_reader.py @@ -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 """ @@ -81,17 +81,26 @@ # Utilities # ============================================================================= - 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: @@ -1612,7 +1621,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(): diff --git a/plugins/docs-tools/skills/jira-reader/SKILL.md b/plugins/docs-tools/skills/jira-reader/SKILL.md index b479d007..bc1b3a17 100644 --- a/plugins/docs-tools/skills/jira-reader/SKILL.md +++ b/plugins/docs-tools/skills/jira-reader/SKILL.md @@ -33,7 +33,7 @@ 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 @@ -41,7 +41,7 @@ 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 diff --git a/plugins/docs-tools/skills/jira-reader/scripts/jira_reader.py b/plugins/docs-tools/skills/jira-reader/scripts/jira_reader.py index 53b66825..7849da42 100755 --- a/plugins/docs-tools/skills/jira-reader/scripts/jira_reader.py +++ b/plugins/docs-tools/skills/jira-reader/scripts/jira_reader.py @@ -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: @@ -145,7 +155,10 @@ 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") @@ -155,7 +168,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: diff --git a/plugins/docs-tools/skills/jira-writer/SKILL.md b/plugins/docs-tools/skills/jira-writer/SKILL.md index ef570dbb..d8043253 100644 --- a/plugins/docs-tools/skills/jira-writer/SKILL.md +++ b/plugins/docs-tools/skills/jira-writer/SKILL.md @@ -24,7 +24,7 @@ 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 @@ -32,7 +32,7 @@ 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 diff --git a/plugins/docs-tools/skills/jira-writer/scripts/jira_writer.py b/plugins/docs-tools/skills/jira-writer/scripts/jira_writer.py index ae2dddef..f006c704 100755 --- a/plugins/docs-tools/skills/jira-writer/scripts/jira_writer.py +++ b/plugins/docs-tools/skills/jira-writer/scripts/jira_writer.py @@ -27,15 +27,25 @@ 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) # Custom field IDs used in Red Hat JIRA @@ -52,7 +62,10 @@ 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") @@ -61,7 +74,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)) else: From 2090a282c9f60da1b8ffb642dfd3645d50460a93 Mon Sep 17 00:00:00 2001 From: Aidan Reilly Date: Sun, 3 May 2026 13:21:00 +0100 Subject: [PATCH 2/4] fix: load project .env before ~/.env so local settings take precedence Swap the shell script load order to match the Python scripts: project .env first (local overrides), then ~/.env (global defaults). Since _safe_load_env only sets unset keys, first-loaded wins. Co-Authored-By: Claude Opus 4.6 rh-pre-commit.version: 2.3.2 rh-pre-commit.check-secrets: ENABLED --- .../docs-workflow-create-jira/scripts/create-jira-ticket.sh | 4 ++-- .../docs-workflow-jira-ready/scripts/jira-ready-check.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/docs-tools/skills/docs-workflow-create-jira/scripts/create-jira-ticket.sh b/plugins/docs-tools/skills/docs-workflow-create-jira/scripts/create-jira-ticket.sh index f38ce033..38c0a74f 100755 --- a/plugins/docs-tools/skills/docs-workflow-create-jira/scripts/create-jira-ticket.sh +++ b/plugins/docs-tools/skills/docs-workflow-create-jira/scripts/create-jira-ticket.sh @@ -15,7 +15,7 @@ PLAN_FILE="${3:?Missing PLAN_FILE argument}" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -# Load global defaults, then local overrides (resolve .env from project root) +# 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" @@ -36,11 +36,11 @@ _safe_load_env() { fi done < "$file" } -_safe_load_env ~/.env _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 +_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}" diff --git a/plugins/docs-tools/skills/docs-workflow-jira-ready/scripts/jira-ready-check.sh b/plugins/docs-tools/skills/docs-workflow-jira-ready/scripts/jira-ready-check.sh index 8d810492..54e20de1 100755 --- a/plugins/docs-tools/skills/docs-workflow-jira-ready/scripts/jira-ready-check.sh +++ b/plugins/docs-tools/skills/docs-workflow-jira-ready/scripts/jira-ready-check.sh @@ -79,11 +79,11 @@ _safe_load_env() { fi done < "$file" } -_safe_load_env ~/.env _project_root="$(cd "$SCRIPT_DIR" 2>/dev/null && git rev-parse --show-toplevel 2>/dev/null || true)" 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:-}}" From ded4bfd403c451d1e5f05e3f0c41d2d7c8d73433 Mon Sep 17 00:00:00 2001 From: Aidan Reilly Date: Mon, 4 May 2026 09:35:43 +0100 Subject: [PATCH 3/4] fix: resolve .env from caller's project root, not plugin install dir jira-ready-check.sh now tries CWD's git root first for .env loading, falling back to SCRIPT_DIR only if CWD is not in a git repo. Co-Authored-By: Claude Opus 4.6 rh-pre-commit.version: 2.3.2 rh-pre-commit.check-secrets: ENABLED --- .../docs-workflow-jira-ready/scripts/jira-ready-check.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/docs-tools/skills/docs-workflow-jira-ready/scripts/jira-ready-check.sh b/plugins/docs-tools/skills/docs-workflow-jira-ready/scripts/jira-ready-check.sh index 54e20de1..10e42956 100755 --- a/plugins/docs-tools/skills/docs-workflow-jira-ready/scripts/jira-ready-check.sh +++ b/plugins/docs-tools/skills/docs-workflow-jira-ready/scripts/jira-ready-check.sh @@ -79,7 +79,10 @@ _safe_load_env() { fi done < "$file" } -_project_root="$(cd "$SCRIPT_DIR" 2>/dev/null && git rev-parse --show-toplevel 2>/dev/null || true)" +_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 From 9bd81f6ceeb5b07ffb854e10e9411d2dc2c46c59 Mon Sep 17 00:00:00 2001 From: Aidan Reilly Date: Mon, 4 May 2026 09:36:22 +0100 Subject: [PATCH 4/4] format plugins rh-pre-commit.version: 2.3.2 rh-pre-commit.check-secrets: ENABLED --- .../docs-tools/skills/git-pr-reader/scripts/git_pr_reader.py | 1 + plugins/docs-tools/skills/jira-reader/scripts/jira_reader.py | 3 +-- plugins/docs-tools/skills/jira-writer/scripts/jira_writer.py | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/docs-tools/skills/git-pr-reader/scripts/git_pr_reader.py b/plugins/docs-tools/skills/git-pr-reader/scripts/git_pr_reader.py index 5f5e3cec..77136aaf 100755 --- a/plugins/docs-tools/skills/git-pr-reader/scripts/git_pr_reader.py +++ b/plugins/docs-tools/skills/git-pr-reader/scripts/git_pr_reader.py @@ -81,6 +81,7 @@ # Utilities # ============================================================================= + def load_env_file() -> None: """Load environment variables from .env files. diff --git a/plugins/docs-tools/skills/jira-reader/scripts/jira_reader.py b/plugins/docs-tools/skills/jira-reader/scripts/jira_reader.py index 7849da42..b24260c7 100755 --- a/plugins/docs-tools/skills/jira-reader/scripts/jira_reader.py +++ b/plugins/docs-tools/skills/jira-reader/scripts/jira_reader.py @@ -156,8 +156,7 @@ 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 (or JIRA_AUTH_TOKEN) not set." - " Add it to .env or ~/.env" + "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") diff --git a/plugins/docs-tools/skills/jira-writer/scripts/jira_writer.py b/plugins/docs-tools/skills/jira-writer/scripts/jira_writer.py index f006c704..32417f19 100755 --- a/plugins/docs-tools/skills/jira-writer/scripts/jira_writer.py +++ b/plugins/docs-tools/skills/jira-writer/scripts/jira_writer.py @@ -63,8 +63,7 @@ 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 (or JIRA_AUTH_TOKEN) not set." - " Add it to .env or ~/.env" + "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")