From test-plan
Publish test plan artifacts to GitHub — creates a branch, commits all artifacts, and opens a PR with optional reviewer assignment. Use after test plan review to make artifacts available for team collaboration and formal review feedback.
npx claudepluginhub opendatahub-io/skills-registry --plugin test-plan[FEATURE_SOURCE] [--repo owner/repo] [--reviewers user1,user2]This skill uses the workspace's default tool permissions.
Publish test plan artifacts to GitHub by creating a branch, committing all files, and opening a pull request.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
Publish test plan artifacts to GitHub by creating a branch, committing all files, and opening a pull request.
/test-plan-publish [FEATURE_DIR] [--repo <owner/repo>] [--reviewers <user1,user2>]
Examples:
/test-plan-publish tool_calling_metadata/test-plan-publish tool_calling_metadata --reviewers alice,bob/test-plan-publish tool_calling_metadata --repo org/test-plans-repo/test-plan-publish (auto-detects feature directory from prior /test-plan-create run)Parse $ARGUMENTS to extract:
mcp_catalog or /path/to/mcp_cataloghttps://github.com/org/repo/tree/test-plan/RHAISTRAT-400https://github.com/org/repo/pull/5--repo (optional): Target GitHub repository in owner/repo format. Defaults to the current repository or detected from GitHub URL.--reviewers (optional): Comma-separated list of GitHub usernames to assign as PR reviewers. If not provided, uses the reviewers field from TestPlan.md frontmatter. If both are empty, no reviewers are assigned.If no feature directory is provided and a TestPlan.md was just generated by /test-plan-create in this session, use its feature directory automatically — proceed directly to Step 0.
If no feature directory can be determined, ask the user via AskUserQuestion:
Where is the test plan to publish?
You can provide:
- Local directory path (e.g.,
~/Code/collection-tests/mcp_catalog)- GitHub branch URL (e.g.,
https://github.com/org/repo/tree/test-plan/RHAISTRAT-400)- GitHub PR URL (e.g.,
https://github.com/org/repo/pull/5)
Install the test-plan package (makes all scripts importable):
(cd $(git -C ${CLAUDE_SKILL_DIR} rev-parse --show-toplevel) && uv sync --extra dev)
If installation fails, inform the user and do NOT proceed. Once installed, all Python scripts will work from any directory.
Run gh auth status via Bash. If it fails, inform the user that gh CLI must be installed and authenticated. Do NOT proceed until this succeeds.
Use the shared locate-feature-dir utility to find the test plan:
result=$(cd $(git -C ${CLAUDE_SKILL_DIR} rev-parse --show-toplevel) && uv run python scripts/repo.py locate-feature-dir "<source>")
if [ $? -ne 0 ]; then
echo "$result"
exit 1
fi
# Parse JSON output
feature_dir=$(echo "$result" | jq -r '.feature_dir')
source_type=$(echo "$result" | jq -r '.source_type')
# If GitHub source, extract repo info for auto-detecting --repo flag
if [ "$source_type" = "github" ]; then
repo_owner=$(echo "$result" | jq -r '.repo_owner')
repo_name=$(echo "$result" | jq -r '.repo_name')
# Auto-set target_repo if --repo flag wasn't provided
[ -z "$target_repo" ] && target_repo="$repo_owner/$repo_name"
fi
Check that the feature directory contains at least:
TestPlan.mdREADME.mdIf TestPlanGaps.md or test_cases/ exist, they will be included. Do NOT fail if they are absent — the user may publish before generating test cases.
Validate the TestPlan.md frontmatter. If validation fails, show the errors and do NOT proceed. The user must fix frontmatter issues before publishing.
(cd $(git -C ${CLAUDE_SKILL_DIR} rev-parse --show-toplevel) && uv run python scripts/frontmatter.py validate <feature_dir>/TestPlan.md)
If TestPlanGaps.md exists, validate it too:
(cd $(git -C ${CLAUDE_SKILL_DIR} rev-parse --show-toplevel) && uv run python scripts/frontmatter.py validate <feature_dir>/TestPlanGaps.md)
If test_cases/TC-*.md files exist, validate them:
(cd $(git -C ${CLAUDE_SKILL_DIR} rev-parse --show-toplevel) && uv run python scripts/validate_test_cases.py <feature_dir> test-case)
Check if there are uncommitted changes in the feature directory. This is informational only — warn the user if there are unstaged changes, but do not block.
# Check if feature_dir is in a git repo
if git -C <feature_dir> rev-parse --git-dir > /dev/null 2>&1; then
changes=$(git -C <feature_dir> status --porcelain .)
if [ -n "$changes" ]; then
echo "⚠ Uncommitted changes detected:"
echo "$changes"
else
echo "✓ Working directory is clean"
fi
else
echo "ℹ Not a git repository - skipping clean state check"
fi
Note: Use variable name changes instead of status (which is readonly in zsh).
<feature_dir>/TestPlan.md using Bash:
(cd $(git -C ${CLAUDE_SKILL_DIR} rev-parse --show-toplevel) && uv run python scripts/frontmatter.py read <feature_dir>/TestPlan.md)
source_key, version, feature, reviewerstest-plan/<source_key> (version not included)
test-plan/RHAISTRAT-400/test-plan-update to push updates to the same PR instead of creating new PRs for each version bump--reviewers argument if provided, otherwise use frontmatter reviewers fieldIMPORTANT: Test plans must NOT be published to the skill repository.
Export CLAUDE_SKILL_DIR for validation utilities:
export CLAUDE_SKILL_DIR
If --repo was provided in arguments:
if ! (cd $(git -C ${CLAUDE_SKILL_DIR} rev-parse --show-toplevel) && uv run python scripts/repo.py validate-remote "$provided_repo"); then
echo "Suggested: fege/collection-tests"
exit 1
fi
target_repoIf --repo was NOT provided: Ask user via AskUserQuestion for a text input (NOT a menu):
Where should this test plan be published?
Specify the target GitHub repository in
owner/repoformat, or press Enter for default:fege/collection-tests
Validate the user-provided or default repository:
owner/repo)if ! (cd $(git -C ${CLAUDE_SKILL_DIR} rev-parse --show-toplevel) && uv run python scripts/repo.py validate-remote "$target_repo"); then
echo "Please specify a different repository."
# Ask again via AskUserQuestion
fi
Store the final validated value in target_repo
Rationale: Prevents accidental (or intentional) publishing of test plans to the skill repository, which would pollute the skill codebase with test plan artifacts and cause branch-switching issues for contributors.
Before creating the branch and PR, present a summary to the user via AskUserQuestion:
Ready to publish test plan
- Feature:
<feature>- Strategy:
<source_key>- Version:
<version>- Branch:
test-plan/<source_key>- Target repo:
<target_repo>- Reviewers:
<reviewer list>(or "none")- Files to publish:
<feature_dir>/TestPlan.md<feature_dir>/README.md<feature_dir>/TestPlanGaps.md(if exists)<feature_dir>/test_cases/(N files, if exists)Proceed? (yes/no)
If the user declines, stop.
status to In Review:
(cd $(git -C ${CLAUDE_SKILL_DIR} rev-parse --show-toplevel) && uv run python scripts/frontmatter.py set <feature_dir>/TestPlan.md status="In Review")
Ensure we're in the correct working directory:
# Get absolute path of feature directory's parent (the repo root)
feature_dir_abs=$(cd "$(dirname "$feature_dir")" && pwd)/$(basename "$feature_dir")
publish_repo_root=$(dirname "$feature_dir_abs")
# Export CLAUDE_SKILL_DIR so functions in the script can use it
export CLAUDE_SKILL_DIR
# Check if we're in the skill repo
current_repo=$(git rev-parse --show-toplevel 2>/dev/null || echo "")
# Get skill repo root
skill_parent="${CLAUDE_SKILL_DIR}/../.."
skill_repo_root=$(cd "$skill_parent" && git rev-parse --show-toplevel 2>/dev/null || echo "")
if [ -n "$current_repo" ] && [ -n "$skill_repo_root" ] && [ "$current_repo" = "$skill_repo_root" ]; then
echo "⚠ Currently in skill repository, switching to publish directory"
fi
# Always cd to publish_repo_root
cd "$publish_repo_root" || { echo "❌ Failed to cd to $publish_repo_root"; exit 1; }
echo "✓ Working in: $(pwd)"
# Verify we're in a git repo (or initialize one if needed)
if ! git rev-parse --git-dir > /dev/null 2>&1; then
echo "Initializing git repository"
git init
git config user.name "$(git config --global user.name)"
git config user.email "$(git config --global user.email)"
fi
Set up remote for target repository:
# Set up temporary remote for target repo
git remote add publish-target https://github.com/$target_repo.git 2>/dev/null || {
# Remote might already exist, update it
git remote set-url publish-target https://github.com/$target_repo.git
}
echo "✓ Publishing to: $target_repo"
Safely checkout branch using shared utility (handles uncommitted changes, stale branches):
# Get repo root (feature_dir may be a subdirectory)
repo_root=$(git -C "$feature_dir" rev-parse --show-toplevel)
# Check if branch exists remotely
git fetch publish-target main
if git ls-remote --heads publish-target test-plan/<source_key> | grep -q test-plan/<source_key>; then
echo "Branch test-plan/<source_key> already exists - updating"
# Branch exists - use safe-checkout to handle stale local branches
(cd $(git -C ${CLAUDE_SKILL_DIR} rev-parse --show-toplevel) && uv run python scripts/repo.py safe-checkout "$repo_root" test-plan/<source_key> --remote publish-target)
if [ $? -ne 0 ]; then
echo "Failed to checkout branch safely"
exit 1
fi
else
echo "Creating new branch test-plan/<source_key>"
# New branch - create from main (initial publish workflow)
# First ensure we're on main and up-to-date
(cd $(git -C ${CLAUDE_SKILL_DIR} rev-parse --show-toplevel) && uv run python scripts/repo.py safe-checkout "$repo_root" main --remote publish-target) || exit 1
# Then create new branch from main
git checkout -b test-plan/<source_key>
fi
Rationale:
test-plan/<source_key>) allow updates to push to the same PRsafe-checkout utility for:
mainStage only public artifacts in the feature directory:
# Get feature directory name (relative path from repo root)
feature_name=$(basename "$feature_dir")
# Always stage these required files
git add $feature_name/TestPlan.md $feature_name/README.md
# Stage optional files if they exist
[ -f $feature_name/TestPlanGaps.md ] && git add $feature_name/TestPlanGaps.md
[ -f $feature_name/TestPlanReview.md ] && git add $feature_name/TestPlanReview.md
# Stage test_cases files if they exist
[ -d $feature_name/test_cases ] && git add $feature_name/test_cases/*.md
Important: This selectively stages only the public artifacts (TestPlan.md, TC-*.md, INDEX.md, README.md, TestPlanGaps.md), excluding internal working files like .review-state.json, repo_instructions.md, test_implementation_conventions.md, and test_scores/ which are meant for internal orchestration only.
Check if there are changes to commit:
if ! git diff --cached --quiet; then
# There are staged changes, proceed with commit
echo "✓ Changes detected, creating commit"
else
echo "⚠ No changes to commit - artifacts are already up to date"
exit 0
fi
Commit with a descriptive message. Use a heredoc to avoid shell injection from frontmatter values:
git commit -m "$(cat <<'EOF'
test-plan(<source_key>): publish <feature> v<version>
EOF
)"
Push the branch:
git push publish-target test-plan/<source_key>
Note: Always use regular push (not --force) since we're either creating a new branch or adding commits on top of an existing one.
Clean up the temporary remote:
git remote remove publish-target
Build the PR title: Test Plan: <feature> (v<version>)
Build the PR body by reading Section 1 (Executive Summary) from TestPlan.md. Format it as:
## Test Plan: <feature>
**Strategy**: [<source_key>](https://redhat.atlassian.net/browse/<source_key>)
**Version**: <version>
### Summary
<Section 1.1 Purpose content>
### Scope
<Section 1.2 Scope — in-scope items only, as bullet list>
### Test Objectives
<Section 1.3 Test Objectives content>
### Artifacts
- TestPlan.md
- TestPlanGaps.md (if exists)
- test_cases/INDEX.md (if exists, with count)
Check if PR already exists for this branch, and create or update accordingly:
# Check if PR exists for this branch
existing_pr=$(gh pr list --repo $target_repo --head test-plan/<source_key> --json number --jq '.[0].number' 2>/dev/null)
if [ -n "$existing_pr" ]; then
# PR exists - just confirm the push updated it
echo "✓ PR #$existing_pr updated with new commits"
pr_url=$(gh pr view $existing_pr --repo $target_repo --json url --jq '.url')
else
# No PR - create new one
pr_url=$(gh pr create \
--repo $target_repo \
--title "Test Plan: <feature> (v<version>)" \
--body "$(cat <<'EOF'
<pr_body>
EOF
)" \
--base main \
--head test-plan/<source_key> \
$([ -n "$reviewers" ] && echo "--reviewer $reviewers") \
--json url --jq '.url')
fi
Note: When updating an existing PR, the new commits are automatically added. The PR title and body are NOT updated (preserving any manual edits reviewers may have made).
Display the PR URL to the user
Show a summary:
If new PR created:
Published successfully
- PR: <PR_URL>
- Branch:
test-plan/<source_key>- Version:
- Status: frontmatter updated to
In Review- Reviewers: <list or "none assigned">
The test plan is now ready for review.
If existing PR updated:
PR updated successfully
- PR: <PR_URL>
- Branch:
test-plan/<source_key>- Version: <old_version> → <new_version>
- New commits pushed
The PR has been updated with the latest changes.
Switch back to the previous branch:
git checkout -
/test-plan-create for that/test-plan-resolve-feedback for thatversion field — version is set by /test-plan-create and only bumped by /test-plan-resolve-feedback after changes$ARGUMENTS