Skip to content

Conversation

@ibihim
Copy link

@ibihim ibihim commented Oct 30, 2025

Summary

Add new learning plugin with two commands for helping engineers understand code and concepts through structured educational approaches.

Commands

/learning:explain <file|package|concept>

Provides deep, first-principles explanations using WHAT/WHY/HOW structure:

  • WHAT: High-level overview and mental models
  • WHY: Problem being solved and design decisions
  • HOW: Implementation details and mechanisms

Works with:

  • Code files (e.g., pkg/reconciler/controller.go)
  • Packages (e.g., pkg/cache)
  • Technical concepts (e.g., eventual consistency)

/learning:build-from-scratch <topic or technology>

Creates Liz Rice-style "Build Your Own X" walkthroughs:

  • Starts with simplest working example
  • Builds incrementally (one concept per step)
  • Provides runnable code at every step
  • Shows how complexity emerges from simple fundamentals
  • Connects to real-world production systems

Output in .work/learning-build-from-scratch/{topic}/

Changes

  • Added plugins/learning/ directory with plugin structure
  • Created plugin.json metadata
  • Created two command definitions following man page format
  • Created comprehensive README with usage examples
  • Registered plugin in marketplace.json
  • Auto-generated documentation (PLUGINS.md, docs/data.json)

Testing

  • ✓ Validated with make lint (all checks passed)
  • ✓ Plugin follows all repository conventions
  • ✓ Command definitions follow man page format
  • ✓ Proper frontmatter and required sections

@openshift-ci openshift-ci bot requested review from bryan-cox and dgoodwin October 30, 2025 17:11
@openshift-ci openshift-ci bot added the needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. label Oct 30, 2025
@openshift-ci
Copy link

openshift-ci bot commented Oct 30, 2025

Hi @ibihim. Thanks for your PR.

I'm waiting for a github.com member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@theobarberbany
Copy link
Contributor

/ok-to-test

@openshift-ci openshift-ci bot added ok-to-test Indicates a non-member PR verified by an org member that is safe to test. and removed needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Oct 30, 2025
@@ -0,0 +1,174 @@
---
description: Create a Liz Rice-style walkthrough building a concept from first principles with working code
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we are not wanting to get AI's to generate any content as if they were a specific human. Especially without permission, but this is probably a general rule we should establish in the repo regardless. Instead, I'd suggest frame it as what qualities you like about how she presents the material. It's also better for the AI to understand

Copy link
Author

@ibihim ibihim Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, makes sense. I just realized, while playing with the AI, that telling the AI to embody a particular person is very effective.

@openshift-ci
Copy link

openshift-ci bot commented Nov 14, 2025

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: ibihim
Once this PR has been reviewed and has the lgtm label, please assign stleerh for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-merge-robot
Copy link
Contributor

PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@openshift-merge-robot openshift-merge-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Nov 14, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 14, 2025

Walkthrough

Adds three new plugins (learning, olm, must-gather), extensive per-plugin documentation and analyzer scripts, website generation and documentation tooling, a claudelint rule enforcing generated docs, CI/workflow and Makefile updates, and assorted plugin command docs and README/site files.

Changes

Cohort / File(s) Summary
Marketplace & Site Data
\.claude-plugin/marketplace\.json, docs/data\.json
Added public entries for learning, olm, and must-gather in marketplace metadata; inserted learning commands into docs/data.json.
Learning plugin
plugins/learning/*, plugins/learning/.claude-plugin/plugin.json, plugins/learning/commands/*
New plugin descriptor, README and two command spec docs (build-from-scratch.md, explain.md).
OLM plugin
plugins/olm/*, plugins/olm/.claude-plugin/plugin.json, plugins/olm/commands/*.md
New plugin descriptor, README and multiple command docs (approve, catalog, debug, diagnose, install, list, search, status, uninstall, upgrade).
Must-Gather plugin & analyzer scripts
plugins/must-gather/*, plugins/must-gather/.claude-plugin/plugin.json, plugins/must-gather/skills/must-gather-analyzer/scripts/*
New plugin descriptor, README, analyze command docs and a suite of Python analyzer scripts (analyze_clusterversion.py, analyze_clusteroperators.py, analyze_etcd.py, analyze_events.py, analyze_network.py, analyze_nodes.py, analyze_pods.py, analyze_pvs.py).
Documentation site & generation tooling
PLUGINS.md, docs/*, scripts/generate_plugin_docs.py, scripts/build-website.py, Makefile
Added generated PLUGINS.md, full docs site docs/index.html, docs/README.md, scripts to generate PLUGINS.md and docs/data.json, and a new make update target that runs generation and site build.
Linters & enforcement
.claudelint-custom.py, .claudelint.yaml
Added PluginsDocUpToDateRule to run the docs-generation workflow and compare outputs; configured loader and enabled plugins-doc-up-to-date rule.
CI, repo metadata, small docs
.github/workflows/lint-plugins.yml, README.md, OWNERS, various plugin README/commands (git, jira, openshift, prow-job, etc.)
Removed PR path filters and PR-failure comment from workflow; centralized docs link in README and added "Updating Plugin Documentation"; added OWNER entries; many plugin command/skill documentation additions and updates (git, jira, openshift, prow-job, and others).

Sequence Diagram(s)

sequenceDiagram
  participant Dev as Developer / CI
  participant Repo as Repository
  participant Gen as scripts/generate_plugin_docs.py
  participant Build as scripts/build-website.py
  participant Files as PLUGINS.md / docs/data.json
  participant Lint as .claudelint-custom PluginsDocUpToDateRule

  Dev->>Repo: push / run make update
  Note over Repo,Gen: make update invokes generation steps
  Repo->>Gen: run generate_plugin_docs.py
  Gen-->>Files: write PLUGINS.md
  Repo->>Build: run build-website.py
  Build-->>Files: write docs/data.json
  Lint->>Repo: (on check) executes PluginsDocUpToDateRule
  PluginsDocUpToDateRule->>Gen: run generate_plugin_docs.py (sandboxed)
  PluginsDocUpToDateRule->>Build: run build-website.py (if present)
  PluginsDocUpToDateRule->>Files: compare generated vs committed
  alt differences found
    PluginsDocUpToDateRule-->>Dev: report violation (restore originals)
  else no diffs
    PluginsDocUpToDateRule-->>Dev: pass
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing focused review:
    • scripts/generate_plugin_docs.py and scripts/build-website.py (parsing, frontmatter handling, edge cases, ordering, output determinism).
    • .claudelint-custom.py PluginsDocUpToDateRule (subprocess invocation, timeouts, restore/compare logic, error reporting).
    • New must-gather analyzer Python scripts (correct YAML/JSON parsing, robust path/glob handling, time calculations, exit codes).
    • Cohesion between marketplace entries, plugin .claude-plugin/plugin.json files, docs/data.json, PLUGINS.md and README links (name/version/description consistency).
    • CI workflow change removing PR filters (intentional scope).
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (6)
PLUGINS.md (1)

86-94: Address markdown lint violations in Learning Plugin section.

The markdownlint tool flagged MD036 (emphasis as heading) on Line 94. While this is a minor style issue, ensure the markdown conforms to the repository's lint rules that are checked in CI (make lint).

The link itself looks fine—ensure the linter passes when running the full check.

plugins/learning/README.md (2)

57-72: Fix markdown linting violations (MD036 and style repetition).

The markdownlint tool flagged MD036 violations at lines 57, 63, 69, and 72, where emphasis (bold text in bullet lists) appears before the colon. The linter interprets **Use /learning:explain when you:** as emphasis-as-heading. Restructure these bullet items to avoid the violation.

Apply this diff to resolve the MD036 violations:

-### Use `/learning:explain` when you:
-
-- Encounter unfamiliar code and need to understand it deeply
-- Want to understand design decisions and trade-offs
+### Use `/learning:explain` when you
+
+- Encounter unfamiliar code and need to understand it deeply
+- Understand design decisions and trade-offs

Repeat similarly for the /learning:build-from-scratch section (lines 66–72).


12-12: Specify language identifiers for all fenced code blocks (MD040).

Fenced code blocks at lines 12, 97, 103, 109, and 115 lack language specifiers. Add appropriate language tags (e.g., bash, shell) to satisfy the markdown linter and improve readability.

Examples:

- ```
+ ```bash
  # Add the ai-helpers marketplace
  /plugin marketplace add openshift-eng/ai-helpers

Also applies to: 97-97, 103-103, 109-109, 115-115

plugins/learning/commands/build-from-scratch.md (1)

12-12: Specify language identifiers for code block examples (MD040).

Code block examples at lines 12, 129, 137, 145, and 153 lack language identifiers. Add bash or appropriate language tags to satisfy the linter and improve syntax highlighting.

-```
+```bash
 /learning:build-from-scratch container runtime

Also applies to: 129-129, 137-137, 145-145, 153-153

plugins/learning/commands/explain.md (2)

57-57: Fix markdown linting violations (MD036) in WHAT/WHY/HOW section.

The markdownlint tool flagged MD036 violations where bold-formatted section markers (**WHAT:**, **WHY:**, **HOW:**) appear in the structured explanation section. Restructure as proper markdown headings to satisfy linting rules.

-**WHAT: High-Level Overview**
+#### WHAT: High-Level Overview

Apply similar changes to lines 63 and 69.

Also applies to: 63-63, 69-69


12-12: Specify language identifiers for code block examples (MD040).

Code block examples lack language identifiers at lines 12, 97, 103, 109, and 115. Add appropriate tags (e.g., bash, shell) for linting compliance and syntax highlighting.

-```
+```bash
 /learning:explain pkg/reconciler/controller.go

Also applies to: 97-97, 103-103, 109-109, 115-115

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between abd54a3 and 985521a.

📒 Files selected for processing (7)
  • .claude-plugin/marketplace.json (1 hunks)
  • PLUGINS.md (2 hunks)
  • docs/data.json (1 hunks)
  • plugins/learning/.claude-plugin/plugin.json (1 hunks)
  • plugins/learning/README.md (1 hunks)
  • plugins/learning/commands/build-from-scratch.md (1 hunks)
  • plugins/learning/commands/explain.md (1 hunks)
🧰 Additional context used
🪛 LanguageTool
plugins/learning/README.md

[style] ~63-~63: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ... to explain complex systems to others - Want to build mental models of technical concep...

(REP_WANT_TO_VB)


[style] ~69-~69: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...arn best by building working examples - Need to teach concepts to others - Want to demy...

(REP_NEED_TO_VB)


[style] ~70-~70: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...es - Need to teach concepts to others - Want to demystify complex technologies - Prepar...

(REP_WANT_TO_VB)


[style] ~72-~72: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...technical interviews or presentations - Want to contribute to systems but need foundati...

(REP_WANT_TO_VB)

plugins/learning/commands/build-from-scratch.md

[uncategorized] ~76-~76: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ...the Walkthrough Create a comprehensive markdown document with: *Introduction Section...

(MARKDOWN_NNP)

🪛 markdownlint-cli2 (0.18.1)
plugins/learning/README.md

12-12: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


57-57: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


63-63: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


69-69: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


97-97: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


103-103: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


109-109: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


115-115: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

plugins/learning/commands/explain.md

12-12: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


57-57: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


63-63: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


69-69: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


97-97: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


103-103: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


109-109: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


115-115: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

PLUGINS.md

94-94: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

🔇 Additional comments (5)
plugins/learning/.claude-plugin/plugin.json (1)

1-8: Plugin metadata is well-formed and consistent.

The plugin descriptor looks good—clean JSON structure, correct versioning, and proper author attribution.

.claude-plugin/marketplace.json (1)

37-41: Marketplace registration is properly integrated.

The learning plugin entry is well-structured and correctly positioned in the marketplace plugins list. Description and source path are consistent with the plugin metadata.

plugins/learning/commands/build-from-scratch.md (1)

29-172: Comprehensive and well-structured command documentation.

The four-phase implementation plan (analysis, incremental steps, structure, verification) is clear and actionable. Examples are concrete and progressive. The documentation effectively communicates the educational approach and expected output. The integration of WebSearch guidance (line 36) and structured code organization aligns well with the plugin's pedagogical goals.

docs/data.json (1)

214-234: Plugin registry entry is correctly structured and consistent.

The learning plugin data is properly formatted with accurate command synopses, argument hints, and metadata. The entry follows the established schema and aligns with the marketplace and plugin metadata definitions.

The AI-generated summary mentions the learning plugin entry appears twice in docs/data.json. Please verify there are no duplicate plugin entries that should be consolidated.

plugins/learning/commands/explain.md (1)

27-87: Well-structured, comprehensive command documentation.

The four-phase implementation (Input Analysis, First Principles Analysis, Structured Explanation, Verification and Depth) provides clear guidance on the command's execution flow. The WHAT/WHY/HOW framework is thoroughly explained and aligned with the plugin's educational mission. Examples are practical and cover diverse input types. The inclusion of WebSearch in Phase 1 and rigorous first-principles checking in Phase 4 demonstrates thoughtful design.

stbenjam and others added 17 commits November 14, 2025 18:38
It doesn't work, and folks are used to just looking at required statuses
on their PR's anyway.
Add comprehensive skills for creating different Jira issue types with templates, best practices, and interactive workflows.

Type-Specific Skills:
- create-story: User story format, acceptance criteria, 3 Cs framework
- create-epic: Epic structure, scope guidelines, parent linking
- create-feature: Market problem framework, success criteria, strategic planning
- create-task: Technical task structure, task vs story differentiation
- create-bug: Bug report template, reproduction steps, severity handling

Project-Specific Skills:
- cntrlplane: CNTRLPLANE project conventions (stories, epics, features, tasks)
- ocpbugs: OCPBUGS project conventions (bugs only)

Team-Specific Skills:
- hypershift: HyperShift team component selection (ARO/ROSA/HyperShift)

Architecture:
- No duplication across project/team skills (~1% structural duplication in type skills)
- Each skill is self-contained and independently readable
- Skills provide implementation guidance for Claude when executing /jira:create command

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Add /jira:create command that provides a single interface for creating all Jira issue types (stories, epics, features, tasks, bugs) with intelligent defaults and interactive guidance.

Command Features:
- Single unified command: /jira:create <type> [project] <summary> [options]
- Hybrid workflow: required arguments + optional flags + interactive prompts
- Universal requirements automatically applied (Security: Red Hat Employee, Labels: ai-generated-jira)
- Security validation: scans for credentials and secrets before submission
- Modular skill invocation: automatically loads type, project, and team skills

Supported Issue Types:
- story: User stories with acceptance criteria
- epic: Epics with parent feature linking
- feature: Strategic features with market problem analysis
- task: Technical tasks and operational work
- bug: Bug reports with structured templates

Usage Examples:
- /jira:create story CNTRLPLANE "Add user dashboard"
- /jira:create epic CNTRLPLANE "Mobile redesign" --parent FEATURE-100
- /jira:create bug "Login button doesn't work"
- /jira:create feature CNTRLPLANE "Advanced search"

Documentation follows man-page format with comprehensive sections for implementation, error handling, examples, and best practices.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
This will give indicators of unusual things going on in the cluster at
the time an e2e test failed, and provides helpful context when debugging
some failures.
This is a first pass idea of some skills for analyzing and extracting
basic data (from yamls, no logs yet) from Openshift must-gathers
Update the spec file save location from contrib/ to .work/jira/solve/
for better organization of JIRA solve specification documents.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Add new slash command that generates Conventional Commits format messages
with two operational modes:
- Mode 1: Analyze staged changes and suggest 3 commit messages
- Mode 2: Analyze N commits for rewriting or squashing

Includes comprehensive documentation with usage examples and type reference.
Add Git plugin section to README with command descriptions.
jianzhangbjz and others added 6 commits November 14, 2025 18:38
Add /git:branch-cleanup command to clean up old and merged branches
with support for dry-run, merged-only, and remote deletion modes.
Includes safety checks to prevent deletion of protected branches.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Adds a comprehensive marketplace website to showcase all plugins, commands,
and skills in the ai-helpers repository:

- Interactive plugin browser with search functionality
- Linkable install instructions and plugin details
- Copy-to-clipboard install commands for each plugin
- Responsive design with dark theme
- Auto-generated from plugin metadata via `make update`
- Updated linter to verify docs/data.json stays in sync

The website is served from docs/ for GitHub Pages deployment.

Co-Authored-By: Claude <[email protected]>
…atch commands

Add new plugin for educational purposes with two commands:
- explain: First principles explanations using WHAT/WHY/HOW structure
- build-from-scratch: Progressive walkthroughs building systems from fundamentals

The plugin helps engineers understand code and concepts through structured
learning approaches.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (34)
plugins/git/commands/branch-cleanup.md (1)

10-12: Add language specification to synopsis code block.

For consistency with the repository's markdown standards, consider adding a language specification to the synopsis code block.

Apply this diff:

 ## Synopsis
-```
+```bash
 /git:branch-cleanup [--dry-run] [--merged-only] [--remote]

</blockquote></details>
<details>
<summary>plugins/openshift/commands/destroy-cluster.md (1)</summary><blockquote>

`10-12`: **Add language specification to synopsis code block.**

For consistency with repository conventions, add a language specification to the synopsis code block.



Apply this diff:

```diff
 ## Synopsis
-```
+```bash
 /openshift:destroy-cluster [install-dir]

</blockquote></details>
<details>
<summary>plugins/git/commands/commit-suggest.md (1)</summary><blockquote>

`10-13`: **Add language specification to synopsis code block.**

For consistency with repository markdown standards, add a language specification to the synopsis code block.



Apply this diff:

```diff
 ## Synopsis
-```
+```bash
 /git:commit-suggest       # Analyze staged changes
 /git:commit-suggest [N]     # Analyze last N commits (1-100)

</blockquote></details>
<details>
<summary>plugins/prow-job/skills/prow-job-analyze-test-failure/SKILL.md (1)</summary><blockquote>

`87-109`: **Clarify wildcard copy behavior and fallback approach.**

The wildcard copy on lines 97-99 may not work reliably with `gcloud storage cp` for nested paths, and the note about fallback on line 99 could be clearer. Consider emphasizing the individual file copy approach as the primary method, since the `ls` command on line 91 already provides the full paths.


Apply this diff to clarify the approach:

```diff
-   - Download all matching interval files (use the full paths from the search results):
+   - Download all matching interval files using the full paths from the search results:
      ```bash
-     gcloud storage cp gs://test-platform-results/{bucket-path}/**/e2e-timelines_spyglass_*.json .work/prow-job-analyze-test-failure/{build_id}/logs/ --no-user-output-enabled
+     # For each file found by the ls command above, download individually:
+     gcloud storage cp gs://test-platform-results/{full-path-from-search} .work/prow-job-analyze-test-failure/{build_id}/logs/ --no-user-output-enabled
      ```
-   - If the wildcard copy doesn't work, copy each file individually using the full paths from the search results
plugins/must-gather/skills/must-gather-analyzer/scripts/analyze_clusterversion.py (2)

15-24: Narrow overly broad exception handling in parser and duration helper

Catching bare Exception in parse_clusterversion and calculate_duration masks unexpected errors and trips BLE001. You only need to handle parse/format issues here.

 def parse_clusterversion(file_path: Path) -> Optional[Dict[str, Any]]:
     """Parse the clusterversion YAML file."""
     try:
         with open(file_path, 'r') as f:
             doc = yaml.safe_load(f)
             if doc and doc.get('kind') == 'ClusterVersion':
                 return doc
-    except Exception as e:
+    except (OSError, yaml.YAMLError) as e:
         print(f"Warning: Failed to parse {file_path}: {e}", file=sys.stderr)
     return None
@@
 def calculate_duration(timestamp_str: str) -> str:
     """Calculate duration from timestamp to now."""
     try:
         ts = datetime.fromisoformat(timestamp_str.replace('Z', '+00:00'))
         now = datetime.now(ts.tzinfo)
         delta = now - ts
@@
-        else:
-            return "<1m"
-    except Exception:
-        return ""
+        else:
+            return "<1m"
+    except (TypeError, ValueError):
+        return ""

Also applies to: 35-56


159-197: Clean up lint issues: unnecessary f-strings and unused variable

A few lines are tripping F541/F841 (f-strings without placeholders and an unused image).

-    print(f"\nCONDITIONS:")
+    print("\nCONDITIONS:")
@@
-    if history:
-        print(f"\nUPDATE HISTORY (last 5):")
+    if history:
+        print("\nUPDATE HISTORY (last 5):")
@@
-    if available_updates and isinstance(available_updates, list) and len(available_updates) > 0:
-        print(f"\nAVAILABLE UPDATES ({len(available_updates)}):")
+    if available_updates and isinstance(available_updates, list) and len(available_updates) > 0:
+        print(f"\nAVAILABLE UPDATES ({len(available_updates)}):")
         for i, update in enumerate(available_updates[:5]):
             version = update.get('version', 'unknown')
-            image = update.get('image', '')
             print(f"  {i+1}. {version}")
     elif available_updates is None:
-        print(f"\nAVAILABLE UPDATES: Unable to retrieve updates")
+        print("\nAVAILABLE UPDATES: Unable to retrieve updates")
plugins/must-gather/skills/must-gather-analyzer/scripts/analyze_etcd.py (2)

16-31: Remove unused endpoint_health parsing to simplify data structure

endpoint_health.json is parsed twice (once into member_health, once into endpoint_health), but the endpoint_health key is never read. This is confusing copy‑paste cruft.

 def parse_etcd_info(must_gather_path: Path) -> Dict[str, Any]:
     """Parse etcd_info directory for cluster health information."""
     etcd_data = {
         'member_health': [],
         'member_list': [],
-        'endpoint_health': [],
-        'endpoint_status': []
+        'endpoint_status': [],
     }
@@
-    # Parse endpoint health
-    endpoint_health_file = etcd_info_dir / "endpoint_health.json"
-    if endpoint_health_file.exists():
-        try:
-            with open(endpoint_health_file, 'r') as f:
-                data = json.load(f)
-                etcd_data['endpoint_health'] = data if isinstance(data, list) else [data]
-        except Exception as e:
-            print(f"Warning: Failed to parse endpoint_health.json: {e}", file=sys.stderr)
-

Also applies to: 55-64


39-41: Tighten exception handling and clean constant f-strings

The JSON parsing blocks catch bare Exception, and a few log lines are f-strings with no interpolation, both of which Ruff flags.

-        except Exception as e:
+        except (OSError, json.JSONDecodeError) as e:
             print(f"Warning: Failed to parse endpoint_health.json: {e}", file=sys.stderr)
@@
-        except Exception as e:
+        except (OSError, json.JSONDecodeError) as e:
             print(f"Warning: Failed to parse member_list.json: {e}", file=sys.stderr)
@@
-        except Exception as e:
+        except (OSError, json.JSONDecodeError) as e:
             print(f"Warning: Failed to parse endpoint_health.json: {e}", file=sys.stderr)
@@
-        except Exception as e:
+        except (OSError, json.JSONDecodeError) as e:
             print(f"Warning: Failed to parse endpoint_status.json: {e}", file=sys.stderr)
@@
-    print(f"\n{'='*80}")
-    print(f"ETCD CLUSTER SUMMARY")
-    print(f"{'='*80}")
+    print(f"\n{'='*80}")
+    print("ETCD CLUSTER SUMMARY")
+    print(f"{'='*80}")
@@
-    if healthy_members < total_members:
-        print(f"  ⚠️  Warning: Not all members are healthy!")
-    elif healthy_members == total_members and total_members > 0:
-        print(f"  ✅ All members healthy")
+    if healthy_members < total_members:
+        print("  ⚠️  Warning: Not all members are healthy!")
+    elif healthy_members == total_members and total_members > 0:
+        print("  ✅ All members healthy")

Also applies to: 52-54, 62-64, 72-74, 147-155

plugins/must-gather/skills/must-gather-analyzer/scripts/analyze_clusteroperators.py (2)

15-24: Avoid catching bare Exception in ClusterOperator parsing and duration logic

Similar to the other analyzers, the broad Exception catches here hide unexpected failures.

 def parse_clusteroperator(file_path: Path) -> Optional[Dict[str, Any]]:
     """Parse a single clusteroperator YAML file."""
     try:
         with open(file_path, 'r') as f:
             doc = yaml.safe_load(f)
             if doc and doc.get('kind') == 'ClusterOperator':
                 return doc
-    except Exception as e:
+    except (OSError, yaml.YAMLError) as e:
         print(f"Warning: Failed to parse {file_path}: {e}", file=sys.stderr)
     return None
@@
 def calculate_duration(timestamp_str: str) -> str:
@@
-        elif minutes > 0:
-            return f"{minutes}m"
-        else:
-            return "<1m"
-    except Exception:
-        return "unknown"
+        elif minutes > 0:
+            return f"{minutes}m"
+        else:
+            return "<1m"
+    except (TypeError, ValueError):
+        return "unknown"

Also applies to: 41-62


41-72: Consider sharing duration/condition helpers across analyzer scripts

calculate_duration / get_condition_duration here are nearly identical to helpers in the ClusterVersion and events analyzers. Extracting them into a small shared utility (e.g., time_utils.py) would reduce duplication and keep behavior consistent if you tweak the formatting later.

plugins/must-gather/skills/must-gather-analyzer/scripts/analyze_pvs.py (1)

15-23: Tighten YAML parse error handling and drop unnecessary f-string

The generic except Exception and a constant f-string in the summary are low-hanging cleanup that also satisfy BLE001/F541.

 def parse_yaml_file(file_path: Path) -> Optional[Dict[str, Any]]:
     """Parse a YAML file."""
     try:
         with open(file_path, 'r') as f:
             doc = yaml.safe_load(f)
             return doc
-    except Exception as e:
+    except (OSError, yaml.YAMLError) as e:
         print(f"Warning: Failed to parse {file_path}: {e}", file=sys.stderr)
     return None
@@
-    print(f"\n{'='*80}")
-    print(f"SUMMARY")
+    print(f"\n{'='*80}")
+    print("SUMMARY")
     print(f"PVs: {total_pvs} total ({bound_pvs} bound, {available_pvs} available)")
     print(f"PVCs: {total_pvcs} total ({bound_pvcs} bound, {pending_pvcs} pending)")

Also applies to: 200-203

plugins/must-gather/skills/must-gather-analyzer/scripts/analyze_events.py (2)

17-31: Replace bare Exception catches in event parsing and age calculation

The event parser and age helper both use broad Exception, which can hide unexpected bugs and are flagged by BLE001.

 def parse_events_file(file_path: Path) -> List[Dict[str, Any]]:
@@
-    try:
-        with open(file_path, 'r') as f:
-            docs = yaml.safe_load_all(f)
-            for doc in docs:
-                if doc and doc.get('kind') == 'Event':
-                    events.append(doc)
-                elif doc and doc.get('kind') == 'EventList':
-                    # Handle EventList
-                    events.extend(doc.get('items', []))
-    except Exception as e:
+    try:
+        with open(file_path, 'r') as f:
+            docs = yaml.safe_load_all(f)
+            for doc in docs:
+                if doc and doc.get('kind') == 'Event':
+                    events.append(doc)
+                elif doc and doc.get('kind') == 'EventList':
+                    # Handle EventList
+                    events.extend(doc.get('items', []))
+    except (OSError, yaml.YAMLError) as e:
         print(f"Warning: Failed to parse {file_path}: {e}", file=sys.stderr)
@@
 def calculate_age(timestamp_str: str) -> str:
@@
-        elif minutes > 0:
-            return f"{minutes}m"
-        else:
-            return "<1m"
-    except Exception:
-        return ""
+        elif minutes > 0:
+            return f"{minutes}m"
+        else:
+            return "<1m"
+    except (TypeError, ValueError):
+        return ""

Also applies to: 34-55


59-63: Remove unused name and avoid ambiguous ℹ character

Minor cleanups to satisfy Ruff: drop the unused name local and replace the ambiguous symbol.

 def format_event(event: Dict[str, Any]) -> Dict[str, Any]:
@@
-    metadata = event.get('metadata', {})
-
-    namespace = metadata.get('namespace', '')
-    name = metadata.get('name', 'unknown')
+    metadata = event.get('metadata', {})
+
+    namespace = metadata.get('namespace', '')
@@
-    print(f"\nShowing {total} most recent events")
-    if warnings > 0:
-        print(f"  ⚠️  {warnings} Warning events")
-    if normal > 0:
-        print(f"  ℹ️  {normal} Normal events")
+    print(f"\nShowing {total} most recent events")
+    if warnings > 0:
+        print(f"  ⚠️  {warnings} Warning events")
+    if normal > 0:
+        print(f"  i  {normal} Normal events")

Also applies to: 163-167

plugins/must-gather/skills/must-gather-analyzer/SKILL.md (1)

56-206: Consider adding language specifiers to code blocks for better syntax highlighting.

The documentation is comprehensive and well-structured. To enhance readability, consider adding bash language specifiers to the shell command code blocks throughout the file (lines 60, 74, 84, etc.). This enables proper syntax highlighting in markdown renderers.

Example for lines 60-62:

-```
+```bash
 ./scripts/analyze_clusterversion.py <must-gather-path>

Also, the bare URL at line 135 could be formatted as a proper markdown link for better rendering.

</blockquote></details>
<details>
<summary>plugins/git/README.md (1)</summary><blockquote>

`11-13`: **Consider hyphenating the compound modifier.**

The term "Conventional Commits" used as a modifier before "commit messages" should be hyphenated: "Conventional Commits-style commit messages". This follows standard English grammar for compound modifiers and improves clarity.



```diff
-Generate Conventional Commits style commit messages for staged changes or recent commits.
+Generate Conventional Commits-style commit messages for staged changes or recent commits.

Based on static analysis hints.

plugins/learning/README.md (1)

1-125: LGTM! Comprehensive and well-structured plugin documentation.

The README provides excellent documentation for the learning plugin with clear explanations of both commands, their use cases, and learning philosophy. The examples and installation instructions are practical and actionable.

Optional improvements for enhanced readability:

  1. Add language specifiers to code blocks (lines 21, 41, 87, 97) for syntax highlighting:

    -```
    +```bash
  2. Consider varying the sentence structure in the "When to Use" sections (lines 59-72) to reduce repetition of "Want to" and "Need to" phrases. For example:

    • "Want to understand..." → "You need to understand..."
    • "Need to teach..." → "You're teaching..."
  3. The bold text at lines 78 and 94 ("First Principles Thinking" and "Learning by Building") could be converted to proper subheadings (####) for better document structure.

These are minor stylistic improvements and don't affect the quality of the documentation.

plugins/olm/commands/upgrade.md (1)

10-12: Fix markdown linting issues: add language specs to code blocks and wrap bare URLs

Several instances of:

  1. MD040: Fenced code blocks missing language specification (e.g., lines 10, 80, 107, 120, 134, 145, 159, 188, 206, 238, 243, 248, 254, 259)
  2. MD034: Bare URL at line 77 should be wrapped in markdown link syntax

Add bash language identifiers to all bash command examples and wrap the documentation link at line 77 in markdown syntax: [link text](url).

Also applies to: 77-77, 80-80, 107-107, 120-120, 134-134, 145-145, 159-159, 188-188, 206-206, 238-238, 243-243, 248-248, 254-254, 259-259

plugins/olm/README.md (1)

301-301: Address markdown linting: code block language specs and URL wrapping

This comprehensive documentation has good content, but several markdown linting issues should be fixed:

  1. MD040 (lines 301, 313, 318, 323): Add language identifiers to fenced code blocks (use bash or appropriate language)
  2. MD034 (lines 506, 533): Wrap bare URLs in markdown link syntax

Also applies to: 313-313, 318-318, 323-323, 506-506, 533-533

plugins/olm/commands/catalog.md (1)

10-10: Markdown linting issues in OLM catalog command documentation

Good comprehensive documentation. Fix these markdown linting violations:

  1. MD040: Code blocks at lines 10, 96, 186, 191, 196 missing language specification (add bash or yaml)
  2. MD034: Bare URLs at lines 207, 213-217 should be wrapped in markdown link syntax

Also applies to: 96-96, 186-186, 191-191, 196-196, 207-207, 213-217

plugins/openshift/commands/bump-deps.md (1)

12-12: Add language identifiers to fenced code blocks.

Multiple code blocks are missing language specifications. For consistency with Markdown best practices and to enable syntax highlighting, add language identifiers (e.g., ```bash, ```output) to each fenced code block throughout the file.

Example:

- ```
+ ```bash
  oc get crd -o json
- ```
+ ```

Also applies to: 177-177, 183-183, 209-209, 215-215, 240-240, 246-246, 291-291, 297-297, 312-312, 318-318

plugins/olm/commands/install.md (2)

243-243: Convert bare URL to markdown link format.

Line 243 contains a bare URL which should be formatted as a markdown link for consistency and accessibility.

-  Reference: https://docs.redhat.com/en/documentation/openshift_container_platform/4.20/html/operators/administrator-tasks#olm-approving-operator-upgrades_olm-updating-operators
+  Reference: [Approving operator upgrades](https://docs.redhat.com/en/documentation/openshift_container_platform/4.20/html/operators/administrator-tasks#olm-approving-operator-upgrades_olm-updating-operators)

10-10: Add language identifiers to fenced code blocks.

Multiple code blocks are missing language specifications. Add language identifiers (e.g., ```bash, ```yaml) to enable proper syntax highlighting.

Also applies to: 113-113, 131-131, 180-180, 190-190, 196-196, 202-202, 208-208, 213-213, 219-219

plugins/olm/commands/diagnose.md (1)

10-10: Add language identifiers to fenced code blocks.

Multiple code blocks lack language specifications. Add identifiers (e.g., ```bash, ```json) for syntax highlighting consistency.

Also applies to: 69-69, 100-100, 135-135, 169-169, 195-195, 222-222, 245-245, 264-264

plugins/olm/commands/list.md (1)

10-10: Add language identifiers to fenced code blocks.

Multiple code blocks are missing language specifications. Add identifiers (e.g., ```bash) for consistency and syntax highlighting.

Also applies to: 80-80, 102-102, 125-125, 130-130

plugins/jira/commands/create.md (1)

10-10: Add language identifiers to fenced code blocks.

Multiple code blocks are missing language specifications. Add identifiers (e.g., ```bash, ```json) for syntax highlighting and consistency.

Also applies to: 199-199, 213-213, 219-219, 225-225, 231-231, 237-237, 243-243, 249-249, 255-255, 309-309, 329-329, 340-340, 353-353, 367-367, 383-383, 422-422, 428-428, 434-434, 440-440

plugins/olm/commands/status.md (2)

77-77: Convert bare URL to markdown link.

Line 77 contains a bare URL. Format it as a markdown link for better accessibility and consistency.

- Reference: https://docs.redhat.com/en/documentation/openshift_container_platform/4.20/html/operators/administrator-tasks#olm-approving-operator-upgrades_olm-updating-operators
+ Reference: [Approving operator upgrades](https://docs.redhat.com/en/documentation/openshift_container_platform/4.20/html/operators/administrator-tasks#olm-approving-operator-upgrades_olm-updating-operators)

10-10: Add language identifiers to fenced code blocks.

Multiple code blocks are missing language specifications. Add identifiers (e.g., ```bash, ```yaml) for syntax highlighting.

Also applies to: 80-80, 107-107, 120-120, 134-134, 145-145, 159-159, 188-188, 206-206, 238-238, 243-243, 248-248, 254-254, 259-259

plugins/olm/commands/approve.md (2)

301-303: Convert bare URLs to markdown links.

Lines 301-303 in the "Additional Resources" section contain bare URLs. Format them as markdown links for consistency and better accessibility.

- - [Red Hat OpenShift: Approving Operator Upgrades](https://docs.redhat.com/en/documentation/openshift_container_platform/4.20/html/operators/administrator-tasks#olm-approving-operator-upgrades_olm-updating-operators)
- - [Red Hat OpenShift: Updating Installed Operators](https://docs.redhat.com/en/documentation/openshift_container_platform/4.20/html/operators/administrator-tasks#olm-updating-operators)
- - [Operator Lifecycle Manager Documentation](https://olm.operatorframework.io/)
+ - [Red Hat OpenShift: Approving Operator Upgrades](https://docs.redhat.com/en/documentation/openshift_container_platform/4.20/html/operators/administrator-tasks#olm-approving-operator-upgrades_olm-updating-operators)
+ - [Red Hat OpenShift: Updating Installed Operators](https://docs.redhat.com/en/documentation/openshift_container_platform/4.20/html/operators/administrator-tasks#olm-updating-operators)
+ - [Operator Lifecycle Manager Documentation](https://olm.operatorframework.io/)

10-10: Add language identifiers to fenced code blocks.

Multiple code blocks lack language specifications. Add identifiers (e.g., ```bash, ```output) for syntax highlighting and consistency.

Also applies to: 96-96, 186-186, 191-191, 196-196

docs/index.html (2)

595-626: Use textContent instead of innerHTML for untrusted plugin metadata.

While the plugin data originates from a generated data.json file (lower risk than user input), using innerHTML with dynamic plugin names and descriptions could allow HTML injection if the data source is ever compromised or modified. For defense-in-depth, use textContent for user-facing strings.

Current approach (lines 604, 608, etc.):

<div class="plugin-name">${plugin.name}</div>

Consider using DOM methods with textContent, or ensure all plugin data is HTML-escaped:

const nameEl = document.createElement('div');
nameEl.className = 'plugin-name';
nameEl.textContent = plugin.name;  // Automatically escaped

Alternatively, if continuing with template literals, explicitly escape HTML entities in plugin data during generation.

Also applies to: 680-701


564-581: Add error handling and data validation in loadData().

The loadData() function fetches and processes the data.json file but lacks validation of the expected data structure. If data.json is malformed or missing the plugins key, this could cause runtime errors.

async function loadData() {
    try {
        const response = await fetch('data.json');
        if (!response.ok) {
            throw new Error(`Failed to load data: ${response.status}`);
        }
        const data = await response.json();
        
+       // Validate data structure
+       if (!Array.isArray(data.plugins)) {
+           throw new Error('Invalid data format: plugins is not an array');
+       }
        
        allPlugins = data.plugins;
        // ... rest of function
    } catch (error) {
        console.error('Error loading data:', error);
+       // Display error to user
+       document.getElementById('plugins-grid').innerHTML = 
+           '<div class="no-results show">Failed to load plugins marketplace</div>';
    }
}
plugins/jira/skills/create-story/SKILL.md (1)

43-472: Tighten markdown style: add code-fence languages and avoid bold-only pseudo-headings

Content looks solid; the main issues are stylistic and caught by markdownlint:

  • Many fenced code blocks (examples, prompts, Jira templates) are missing a language identifier (e.g., lines around the user story examples, AC formats, prompts, and Jira markup). Adding text, bash, yaml, or python as appropriate will satisfy MD040 and improve readability.
  • A few lines use bold text as de facto headings (e.g., “Approach 1: Guided Questions”, “Approach 2: Template Assistance”, “Approach 3: Free-Form”). Converting these to proper headings (###/####) will address MD036 and make the structure more scannable.

These are non-functional but worth fixing since you already run markdownlint in CI.

scripts/generate_plugin_docs.py (1)

17-33: Make README lookup robust by tracking plugin path in PluginInfo

Right now the README link is derived as plugins/{plugin.name}/README.md, while plugin.name comes from plugin.json. If a plugin ever uses a display name that doesn’t exactly match its directory name, the README link in PLUGINS.md will break.

You can make this robust (and simplify lookup) by storing the plugin path in PluginInfo and using it when generating docs:

-from pathlib import Path
-from typing import Dict, List, Tuple
+from pathlib import Path
+from typing import Dict, List, Tuple
@@
-class PluginInfo:
-    """Information about a plugin."""
-    
-    def __init__(self, name: str, description: str, version: str):
-        self.name = name
-        self.description = description
-        self.version = version
-        self.commands = []
+class PluginInfo:
+    """Information about a plugin."""
+
+    def __init__(self, name: str, description: str, version: str, path: Path):
+        self.name = name
+        self.description = description
+        self.version = version
+        self.path = path
+        self.commands: List[Dict[str, str]] = []
@@
-    plugin_info = PluginInfo(
-        name=plugin_data.get('name', plugin_dir.name),
-        description=plugin_data.get('description', ''),
-        version=plugin_data.get('version', '0.0.0')
-    )
+    plugin_info = PluginInfo(
+        name=plugin_data.get('name', plugin_dir.name),
+        description=plugin_data.get('description', ''),
+        version=plugin_data.get('version', '0.0.0'),
+        path=plugin_dir,
+    )
@@
-        # Link to plugin README if it exists
-        readme_path = plugins_dir / plugin.name / 'README.md'
+        # Link to plugin README if it exists
+        readme_path = plugin.path / 'README.md'

This keeps PLUGINS.md generation correct even if plugin names and folder names diverge later.

Also applies to: 54-89, 92-146

.claudelint-custom.py (1)

17-31: Align rule messaging with what it actually runs, and consider tightening subprocess/error handling

Functionally this rule does the right thing, but a couple of details could be clearer:

  1. Message vs. behavior mismatch

    • Docstring and description() say “run 'make update'”, and the error message at Line 66 reports "'make update' failed", but the rule actually runs:
      • python3 scripts/generate_plugin_docs.py
      • and optionally python3 scripts/build-website.py
    • To avoid confusion for contributors, either:
      • Change the subprocess call to invoke your make update target, or
      • Update the docstring and messages to say “docs generation scripts” instead of “'make update'”.

    For example:

  •    return "PLUGINS.md and docs/data.json must be up-to-date with plugin metadata. Run 'make update' to regenerate."
    
  •    return "PLUGINS.md and docs/data.json must be up-to-date with plugin metadata. Run 'make update' (or the docs generation scripts) to regenerate."
    

@@

  •                    f"'make update' failed: {result.stderr}",
    
  •                    f"generate_plugin_docs.py failed: {result.stderr}",
    
    
    
  1. Subprocess and exception handling (nice-to-have)
    • Using "python3" relies on PATH; for portability you could use sys.executable instead.
    • The broad except Exception is understandable (you don’t want claudelint to crash), but if you want to satisfy BLE001 you could narrow this to OSError | subprocess.SubprocessError while still turning unexpected issues into a violation.

None of this blocks functionality, but aligning messages with behavior will make this rule less surprising to future maintainers.

Also applies to: 49-91, 120-133

- [Utils](#utils-plugin)
- [Yaml](#yaml-plugin)

### Agendas Plugin
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix heading levels and spelling in centralized plugin documentation

Two issues to address:

  1. MD001 - Heading Level: Line 20 and all subsequent plugin section headings use ### (h3) but should use ## (h2) to properly nest under the document title. This creates an improper heading hierarchy.

  2. Spelling: Line 30 contains "Miscellaenous" which should be "Miscellaneous".

Apply this diff to fix both issues:

-### Agendas Plugin
+## Agendas Plugin

 A plugin to create various meeting agendas

 **Commands:**
 - **`/agendas:outcome-refinement`** - Analyze the list of JIRA outcome issues to prepare an outcome refinement meeting agenda.

 See [plugins/agendas/README.md](plugins/agendas/README.md) for detailed documentation.

-### Ci Plugin
+## Ci Plugin

-Miscellaenous tools for working with OpenShift CI
+Miscellaneous tools for working with OpenShift CI

Note: Apply the heading level change to all 13 plugin sections (lines 20, 29, 42, 51, 64, 73, 86, 96, 105, 123, 136, 147, 156, 168).

Also applies to: 30-30

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

20-20: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)

🤖 Prompt for AI Agents
In PLUGINS.md around lines 20-168 (specifically lines 20, 29, 30, 42, 51, 64,
73, 86, 96, 105, 123, 136, 147, 156, 168), update each plugin section heading
from ### to ## to correct the heading hierarchy (MD001) and correct the spelling
at line 30 from "Miscellaenous" to "Miscellaneous"; apply the heading change to
all 13 plugin section lines listed and save the file.

Comment on lines +1 to +8
{
"name": "must-gather",
"description": "A plugin to analyze and report on must-gather data",
"version": "0.0.1",
"author": {
"name": "openshift"
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Verify author field naming convention against other plugins.

The AI-generated summary indicates the author name should be "github.com/openshift-eng", but the actual file contains "openshift". Please confirm this aligns with the author naming convention used in other plugin.json files in the repository.


🏁 Script executed:

#!/bin/bash
# Description: Check author naming convention across all plugin.json files

# Find all plugin.json files and extract author.name values
fd -e plugin.json -x grep -A1 '"author"' {} + | grep -A1 '"name"'

# Also check if there's documentation or examples specifying the expected format
find . -name "*.md" -o -name "*.json" | xargs grep -l "author.*openshift" 2>/dev/null | head -10

Length of output: 43



---

🏁 Script executed:

```shell
#!/bin/bash
# Find all plugin.json files and display their full contents
echo "=== All plugin.json files found ==="
fd -type f -name 'plugin.json'

echo -e "\n=== Contents of each plugin.json ==="
fd -type f -name 'plugin.json' -x echo "File: {}" \; -x cat {}

# Also search for any author-related patterns in JSON/config files
echo -e "\n=== Files mentioning author in .json files ==="
fd -type f -name '*.json' -x grep -l '"author"' {}

Length of output: 634



---

🏁 Script executed:

```shell
#!/bin/bash
# Find all plugin.json files and display their full contents
echo "=== All plugin.json files found ==="
fd -t f -name 'plugin.json'

echo -e "\n=== Contents of each plugin.json ==="
fd -t f -name 'plugin.json' -exec sh -c 'echo "File: $1"; cat "$1"' _ {} \;

# Also search for any author field in JSON files
echo -e "\n=== All JSON files with author field ==="
fd -t f -name '*.json' -exec grep -l '"author"' {} \;

Length of output: 808



---

🏁 Script executed:

```shell
#!/bin/bash
# Find all plugin.json files using find command
echo "=== All plugin.json files found ==="
find . -name 'plugin.json' -type f

echo -e "\n=== Contents of each plugin.json ==="
find . -name 'plugin.json' -type f -exec sh -c 'echo "File: $1"; cat "$1"' _ {} \;

echo -e "\n=== All JSON files with author field ==="
find . -name '*.json' -type f -exec grep -l '"author"' {} \;

Length of output: 4683


Align author field with repository convention: change "openshift" to "github.com/openshift-eng".

The repository establishes a clear pattern where OpenShift team plugins use "github.com/openshift-eng" as the author name (agendas, git, jira, olm, openshift, utils, learning). The must-gather plugin currently uses "openshift", which diverges from this convention. Update line 6 to maintain consistency.

"author": {
  "name": "github.com/openshift-eng"
}
🤖 Prompt for AI Agents
In plugins/must-gather/.claude-plugin/plugin.json around lines 1 to 8, the
author.name value is "openshift" which deviates from repository convention;
update the author.name field to "github.com/openshift-eng" so it matches other
OpenShift team plugins and keep formatting valid JSON.

Comment on lines +78 to +94
def print_member_health(members: List[Dict[str, Any]]):
"""Print etcd member health status."""
if not members:
print("No member health data found.")
return

print("ETCD MEMBER HEALTH")
print(f"{'ENDPOINT':<60} {'HEALTH':<10} {'TOOK':<10} ERROR")

for member in members:
endpoint = member.get('endpoint', 'unknown')[:60]
health = 'true' if member.get('health') else 'false'
took = member.get('took', '')
error = member.get('error', '')

print(f"{endpoint:<60} {health:<10} {took:<10} {error}")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix etcd health evaluation to avoid misclassifying unhealthy members

Using m.get('health') directly in truthiness checks will treat any non‑empty string (including "false") as healthy, skewing the summary and member health display.

 def print_member_health(members: List[Dict[str, Any]]):
@@
     for member in members:
         endpoint = member.get('endpoint', 'unknown')[:60]
-        health = 'true' if member.get('health') else 'false'
+        raw_health = member.get('health')
+        health = 'true' if str(raw_health).lower() == 'true' else 'false'
         took = member.get('took', '')
         error = member.get('error', '')
@@
 def print_summary(etcd_data: Dict[str, Any]):
@@
-    total_members = len(member_list)
-    healthy_members = sum(1 for m in member_health if m.get('health'))
+    total_members = len(member_list)
+    healthy_members = sum(
+        1 for m in member_health
+        if str(m.get('health')).lower() == 'true'
+    )

Also applies to: 138-151

🤖 Prompt for AI Agents
In plugins/must-gather/skills/must-gather-analyzer/scripts/analyze_etcd.py
around lines 78-94 (and similarly for 138-151), the code uses
member.get('health') directly which treats non-empty strings like "false" as
truthy; update the health evaluation to normalize and explicitly compare the
value to boolean True or the string "true" (case-insensitive) so that "false" is
treated as unhealthy, then use that normalized boolean to set the displayed
"true"/"false" string and any summary counts; update all places that rely on
truthiness to use this normalized check.

Comment on lines +51 to +110
def get_pod_status(pod: Dict[str, Any]) -> Dict[str, Any]:
"""Extract pod status information."""
metadata = pod.get('metadata', {})
status = pod.get('status', {})
spec = pod.get('spec', {})

name = metadata.get('name', 'unknown')
namespace = metadata.get('namespace', 'unknown')
creation_time = metadata.get('creationTimestamp', '')

# Get container statuses
container_statuses = status.get('containerStatuses', [])
init_container_statuses = status.get('initContainerStatuses', [])

# Calculate ready containers
total_containers = len(spec.get('containers', []))
ready_containers = sum(1 for cs in container_statuses if cs.get('ready', False))

# Get overall phase
phase = status.get('phase', 'Unknown')

# Determine more specific status
pod_status = phase
reason = status.get('reason', '')

# Check for specific container states
for cs in container_statuses:
state = cs.get('state', {})
if 'waiting' in state:
waiting = state['waiting']
pod_status = waiting.get('reason', 'Waiting')
elif 'terminated' in state:
terminated = state['terminated']
if terminated.get('exitCode', 0) != 0:
pod_status = terminated.get('reason', 'Error')

# Check init containers
for ics in init_container_statuses:
state = ics.get('state', {})
if 'waiting' in state:
waiting = state['waiting']
if waiting.get('reason') in ['CrashLoopBackOff', 'ImagePullBackOff', 'ErrImagePull']:
pod_status = f"Init:{waiting.get('reason', 'Waiting')}"

# Calculate total restarts
total_restarts = sum(cs.get('restartCount', 0) for cs in container_statuses)

# Calculate age
age = calculate_age(creation_time) if creation_time else ''

return {
'namespace': namespace,
'name': name,
'ready': f"{ready_containers}/{total_containers}",
'status': pod_status,
'restarts': str(total_restarts),
'age': age,
'node': spec.get('nodeName', ''),
'is_problem': pod_status not in ['Running', 'Succeeded', 'Completed'] or total_restarts > 0
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused variable.

Line 74 assigns reason but never uses it. This variable can be safely removed.

Apply this diff:

     # Get overall phase
     phase = status.get('phase', 'Unknown')
 
     # Determine more specific status
     pod_status = phase
-    reason = status.get('reason', '')
 
     # Check for specific container states
     for cs in container_statuses:
🧰 Tools
🪛 Ruff (0.14.4)

74-74: Local variable reason is assigned to but never used

Remove assignment to unused variable reason

(F841)

🤖 Prompt for AI Agents
In plugins/must-gather/skills/must-gather-analyzer/scripts/analyze_pods.py
around lines 51 to 110, remove the unused local variable `reason` assigned from
status.get('reason', '') on line 74; it is never referenced elsewhere, so delete
that assignment to avoid an unused-variable warning and keep the rest of the
function unchanged.

Comment on lines +130 to +132
- If Manual and not approved, display message:
```
⏸️ InstallPlan created but requires manual approval
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix malformed markdown code fence on line 132.

The code block closing on line 132 has only two backticks instead of three, which breaks the markdown fence. This causes the example output to not render correctly.

 2. **Install cert-manager-operator with custom namespace**:
    ```
    /olm:install openshift-cert-manager-operator my-cert-manager
-   ``
+   ```
    This will install the operator in the `my-cert-manager` namespace.

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

131-131: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In plugins/olm/commands/install.md around lines 130 to 132, the closing markdown
code fence is malformed (two backticks) causing the code block to not render;
replace the two backticks on line 132 with a proper three-backtick fence to
correctly close the code block so the example output renders as intended.

Comment on lines +130 to +132
```
/olm:list cert-manager-operator
``
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix malformed markdown code fence on line 132.

Line 132 has only two backticks instead of three, breaking the code block closure. This prevents the example from rendering correctly.

 2. **List operators in a specific namespace**:
    ```
    /olm:list cert-manager-operator
-   ``
+   ```
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
/olm:list cert-manager-operator
``
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

130-130: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In plugins/olm/commands/list.md around lines 130 to 132 the closing markdown
code fence is malformed (only two backticks) which breaks the code block;
replace the two backticks on line 132 with three backticks to properly close the
fenced code block so the example renders correctly.

Comment on lines +146 to +157
```bash
# Parse version from image tag (e.g., "4.21.0-ec.2" or "4.21.0-0.ci-2025-10-27-031915")
VERSION=$(echo "$RELEASE_IMAGE" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+[^"]*' | head -1)
```

### 3. Determine Installer Location and Extract if Needed

```bash
INSTALLER_DIR="${installer-dir:-$HOME/.openshift-installers}"
INSTALLER_PATH="$INSTALLER_DIR/openshift-install-${VERSION}"
```

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Fix invalid shell variable and tighten markdown examples

  1. Shell correctness (should be fixed before merge)
    The snippet that derives INSTALLER_DIR uses an invalid shell variable name:

    INSTALLER_DIR="${installer-dir:-$HOME/.openshift-installers}"

    Shell variable names can’t contain -, so this won’t run as written. If the intent is “use the $INSTALLER_DIR env/flag if set, otherwise default”, it should look like:

-INSTALLER_DIR="${installer-dir:-$HOME/.openshift-installers}"
+INSTALLER_DIR="${INSTALLER_DIR:-$HOME/.openshift-installers}"


Same for later references like `${cluster-name}` / `${base-domain}` in summary examples – for shell they should be `${CLUSTER_NAME}` / `${BASE_DOMAIN}`, or clearly marked as placeholders rather than literal env vars.

2. **Optional markdown polish (lint-driven)**  
- Several fenced blocks (Synopsis, example commands, summary snippets) lack a language identifier; adding `bash`, `yaml`, or `text` where appropriate will silence MD040 and help with highlighting.
- Bare URLs (e.g., oc client download, pull-secret links) can be wrapped as `[text](URL)` if you want to satisfy MD034, but this is cosmetic.

The main blocker here is the invalid variable name; the rest is documentation polish.



Also applies to: 214-299, 404-428

---

I'll verify the shell variable naming rules and check the actual file content.










🏁 Script executed:

#!/bin/bash
# Test whether shell variable names can contain hyphens
bash -c 'installer-dir="test" 2>&1' || true

Length of output: 51


🏁 Script executed:

#!/bin/bash
# Test valid shell variable with underscore vs hyphen
bash -c 'INSTALLER_DIR="test"; echo "$INSTALLER_DIR"'
bash -c 'installer_dir="test"; echo "$installer_dir"'
bash -c '${installer-dir:-default}' 2>&1 || true

Length of output: 55


🏁 Script executed:

# Check if the file exists and show content around the specified lines
fd "create-cluster.md" --type f

Length of output: 44


🏁 Script executed:

# Show lines 146-157, 214-299, 404-428 from the file if it exists
if [ -f "plugins/openshift/commands/create-cluster.md" ]; then
  echo "=== Lines 146-157 ==="; sed -n '146,157p' plugins/openshift/commands/create-cluster.md
  echo -e "\n=== Lines 214-299 ==="; sed -n '214,299p' plugins/openshift/commands/create-cluster.md
  echo -e "\n=== Lines 404-428 ==="; sed -n '404,428p' plugins/openshift/commands/create-cluster.md
else
  echo "File not found"
fi

Length of output: 3574


🌐 Web query:

bash shell variable naming rules allowed characters

💡 Result:

Short answer: bash variable names may contain only letters (A–Z, a–z), digits (0–9) and underscore (_); they must begin with a letter or underscore (not a digit) and are case‑sensitive. Use ${name} when you need to disambiguate following characters. [1][2][3]

Sources:

  • Bash Reference Manual (definition of Name). [1]
  • POSIX / The Open Group (shell name rules). [2]
  • Explanation of parser behavior and allowed characters. [3]

Fix invalid shell variable names with hyphens across documentation

The review's core finding is verified: shell variable names cannot contain hyphens, and the documented snippets will fail when copied.

Confirmed issues:

  • Line 146–157: ${installer-dir:-...} is invalid; must be ${INSTALLER_DIR:-...} or similar (underscore, not hyphen).
  • Line 214–299: ${cluster-name}-install-... has the same pattern and will fail identically.

Both snippets will immediately error with "command not found" when users attempt to run them. The proposed fix (use underscore in variable names) is correct and necessary.

🤖 Prompt for AI Agents
In plugins/openshift/commands/create-cluster.md around lines 146–157 (and also
update the similar occurrences in lines 214–299), the shell examples use invalid
variable names containing hyphens (e.g., installer-dir, cluster-name) which will
cause "command not found" errors; change those variable names to valid
identifiers with underscores (e.g., INSTALLER_DIR, CLUSTER_NAME), update all
template instances and parameter expansions to use the new names (including
defaulting forms like ${INSTALLER_DIR:-...} and ${CLUSTER_NAME}-install-...),
and ensure consistency/casing across the whole document so copied snippets run
correctly in POSIX shells.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. ok-to-test Indicates a non-member PR verified by an org member that is safe to test.

Projects

None yet

Development

Successfully merging this pull request may close these issues.