Skip to content

feat: generate rich profile READMEs with badges, repos, and contributions#3962

Merged
marcusquinn merged 1 commit intomainfrom
feature/rich-profile-init
Mar 9, 2026
Merged

feat: generate rich profile READMEs with badges, repos, and contributions#3962
marcusquinn merged 1 commit intomainfrom
feature/rich-profile-init

Conversation

@marcusquinn
Copy link
Copy Markdown
Owner

Summary

  • cmd_init now generates a rich profile README by fetching the user's GitHub profile data via gh api, instead of seeding a bare template with only stat markers
  • Auto-detects languages from the user's repos and renders shields.io badges (23 languages + Docker/Linux/Git)
  • Generates Projects (non-fork repos), Contributions (forks linked to upstream parent URLs), and Connect (blog, Twitter/X, GitHub follow) sections

Changes

New functions in profile-readme-helper.sh

  • _lang_badge() — maps language name to shields.io badge markdown (23 languages + generic fallback)
  • _generate_rich_readme() — fetches GitHub user profile + repos, generates full README with badges, projects, contributions, connect section, and STATS markers

Modified

  • cmd_init() — calls _generate_rich_readme() instead of writing a bare template

Testing

  • ShellCheck: zero violations
  • Dry-run tested against johnwaldo — correctly detected Ruby/Shell/TypeScript, listed 1 own project, 6 contributions with upstream parent URLs, GitHub follow badge (no blog/twitter)

…ions

cmd_init now calls _generate_rich_readme() which fetches the user's GitHub
profile data and auto-generates a README with:
- Language badges auto-detected from the user's repos (23 languages supported)
- Common tooling badges (Docker, Linux, Git)
- Bio line from GitHub profile
- Projects section (non-fork repos with descriptions)
- Contributions section (forks linked to upstream parent URLs)
- Connect section (blog, Twitter/X, GitHub follow badges)
- STATS markers for aidevops auto-update

Replaces the bare template that only had stat markers.
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly upgrades the profile README generation capability within the system. Instead of a minimal template, the cmd_init command now dynamically creates a feature-rich README by leveraging the GitHub API to pull user profile information, repository details, and language usage. This enhancement provides users with a more informative and visually appealing profile page, automatically showcasing their projects, contributions, and relevant technical skills through badges and structured sections.

Highlights

  • Enhanced Profile README Generation: The cmd_init function now generates a comprehensive profile README by fetching GitHub user data via gh api, replacing the previous basic template seeding.
  • Language Badges: The system automatically detects programming languages from a user's repositories and renders corresponding shields.io badges for 23 languages, plus Docker, Linux, and Git.
  • Structured Content Sections: The generated README includes dedicated sections for 'Projects' (non-fork repositories), 'Contributions' (forks linked to their upstream parents), and 'Connect' (blog, Twitter/X, and GitHub follow links).
  • New Helper Functions: Two new shell functions, _lang_badge() and _generate_rich_readme(), were introduced to handle badge generation and the overall rich README composition, respectively.
Changelog
  • .agents/scripts/profile-readme-helper.sh
    • Added _lang_badge function to map programming language names to shields.io badge markdown, supporting 23 languages and a generic fallback.
    • Implemented _generate_rich_readme function to fetch GitHub user and repository data, dynamically generate language badges, list projects and contributions, and create a 'Connect' section.
    • Modified cmd_init to invoke _generate_rich_readme for creating the initial profile README, replacing the static template generation.
Activity
  • No activity has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions github-actions bot added the enhancement Auto-created from TODO.md tag label Mar 9, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 9, 2026

Important

Review skipped

Auto reviews are limited based on label configuration.

🚫 Review skipped — only excluded labels are configured. (1)
  • no-review

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2af1a783-f232-4044-867e-0718f21a27f8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/rich-profile-init

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 9, 2026

🔍 Code Quality Report

�[0;35m[MONITOR]�[0m Code Review Monitoring Report

�[0;34m[INFO]�[0m Latest Quality Status:
SonarCloud: 0 bugs, 0 vulnerabilities, 395 code smells

�[0;34m[INFO]�[0m Recent monitoring activity:
Mon Mar 9 05:36:56 UTC 2026: Code review monitoring started
Mon Mar 9 05:36:57 UTC 2026: SonarCloud - Bugs: 0, Vulnerabilities: 0, Code Smells: 395

📈 Current Quality Metrics

  • BUGS: 0
  • CODE SMELLS: 395
  • VULNERABILITIES: 0

Generated on: Mon Mar 9 05:36:59 UTC 2026


Generated by AI DevOps Framework Code Review Monitoring

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Mar 9, 2026

@marcusquinn marcusquinn merged commit e73ee70 into main Mar 9, 2026
19 checks passed
@marcusquinn marcusquinn deleted the feature/rich-profile-init branch March 9, 2026 05:39
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a feature to generate rich profile READMEs. However, it introduces a security vulnerability related to Markdown injection and potential Cross-Site Scripting (XSS) due to a lack of proper output encoding for user-controlled fields from the GitHub API embedded directly into the generated README.md file. Additionally, there are performance issues in how the GitHub API data is processed, specifically an N+1 API call problem when handling forked repositories and inefficient use of jq calls within loops.

Comment on lines +701 to +716
local contrib_repos=""
while IFS= read -r row; do
[[ -z "$row" ]] && continue
local rname rdesc rurl
rname=$(echo "$row" | jq -r '.name')
rdesc=$(echo "$row" | jq -r '.description // "No description"')
rurl=$(echo "$row" | jq -r '.html_url')
# Try to get parent repo URL
local parent_url
parent_url=$(gh api "repos/${gh_user}/${rname}" --jq '.parent.html_url // empty' 2>/dev/null)
if [[ -n "$parent_url" ]]; then
contrib_repos="${contrib_repos}- **[${rname}](${parent_url})** -- ${rdesc}"$'\n'
else
contrib_repos="${contrib_repos}- **[${rname}](${rurl})** -- ${rdesc}"$'\n'
fi
done < <(echo "$repos_json" | jq -c '.[] | select(.fork == true)')
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

This loop is inefficient and will perform poorly for users with many forked repositories. It suffers from two issues:

  1. N+1 API Calls: It makes a separate gh api call for every forked repository to fetch the parent URL, which is already available in the initial repos_json data.
  2. Multiple jq Invocations: It calls jq multiple times inside the loop for each repository.

You can resolve both issues by using a single jq command to process the repos_json data, which is significantly more performant and simplifies the code.

Suggested change
local contrib_repos=""
while IFS= read -r row; do
[[ -z "$row" ]] && continue
local rname rdesc rurl
rname=$(echo "$row" | jq -r '.name')
rdesc=$(echo "$row" | jq -r '.description // "No description"')
rurl=$(echo "$row" | jq -r '.html_url')
# Try to get parent repo URL
local parent_url
parent_url=$(gh api "repos/${gh_user}/${rname}" --jq '.parent.html_url // empty' 2>/dev/null)
if [[ -n "$parent_url" ]]; then
contrib_repos="${contrib_repos}- **[${rname}](${parent_url})** -- ${rdesc}"$'\n'
else
contrib_repos="${contrib_repos}- **[${rname}](${rurl})** -- ${rdesc}"$'\n'
fi
done < <(echo "$repos_json" | jq -c '.[] | select(.fork == true)')
contrib_repos=$(echo "$repos_json" | jq -r '.[] | select(.fork == true) | "- **[\\(.name)](\\(.parent.html_url // .html_url))** -- \\(.description // \"No description\")"')
References
  1. Consolidate multiple 'jq' calls into a single pass where possible to improve performance and script efficiency.
  2. Prefer using URLs provided directly in API responses (e.g., 'html_url', 'web_url') instead of manually constructing them via string concatenation to ensure robustness.

Comment on lines +697 to +714
own_repos="${own_repos}- **[${rname}](${rurl})** -- ${rdesc}"$'\n'
done < <(echo "$repos_json" | jq -c ".[] | select(.fork == false and .name != \"${gh_user}\")")

# Build contributions section (forks with description)
local contrib_repos=""
while IFS= read -r row; do
[[ -z "$row" ]] && continue
local rname rdesc rurl
rname=$(echo "$row" | jq -r '.name')
rdesc=$(echo "$row" | jq -r '.description // "No description"')
rurl=$(echo "$row" | jq -r '.html_url')
# Try to get parent repo URL
local parent_url
parent_url=$(gh api "repos/${gh_user}/${rname}" --jq '.parent.html_url // empty' 2>/dev/null)
if [[ -n "$parent_url" ]]; then
contrib_repos="${contrib_repos}- **[${rname}](${parent_url})** -- ${rdesc}"$'\n'
else
contrib_repos="${contrib_repos}- **[${rname}](${rurl})** -- ${rdesc}"$'\n'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-medium medium

This section embeds user-controlled repository names (rname), descriptions (rdesc), and URLs (rurl, parent_url) directly into the Markdown content without proper sanitization. This creates a risk of Markdown injection and potential Cross-Site Scripting (XSS) if an attacker provides malicious data. Additionally, the current while loop invokes jq multiple times for each repository, leading to inefficiency. The suggested code addresses the performance by consolidating jq calls to generate the markdown list directly, but further sanitization of user-controlled fields is recommended to mitigate the XSS vulnerability.

Suggested change
own_repos="${own_repos}- **[${rname}](${rurl})** -- ${rdesc}"$'\n'
done < <(echo "$repos_json" | jq -c ".[] | select(.fork == false and .name != \"${gh_user}\")")
# Build contributions section (forks with description)
local contrib_repos=""
while IFS= read -r row; do
[[ -z "$row" ]] && continue
local rname rdesc rurl
rname=$(echo "$row" | jq -r '.name')
rdesc=$(echo "$row" | jq -r '.description // "No description"')
rurl=$(echo "$row" | jq -r '.html_url')
# Try to get parent repo URL
local parent_url
parent_url=$(gh api "repos/${gh_user}/${rname}" --jq '.parent.html_url // empty' 2>/dev/null)
if [[ -n "$parent_url" ]]; then
contrib_repos="${contrib_repos}- **[${rname}](${parent_url})** -- ${rdesc}"$'\n'
else
contrib_repos="${contrib_repos}- **[${rname}](${rurl})** -- ${rdesc}"$'\n'
own_repos=$(echo "$repos_json" | jq -r ".[] | select(.fork == false and .name != \"${gh_user}\") | \"- **[\\(.name)](\\(.html_url))** -- \\(.description // \"No description\")\"")
References
  1. Consolidate multiple 'jq' calls into a single pass where possible to improve performance and script efficiency.

Haskell) echo '![Haskell](https://img.shields.io/badge/-Haskell-5D4F85?style=flat-square&logo=haskell&logoColor=white)' ;;
Vue) echo '![Vue](https://img.shields.io/badge/-Vue-4FC08D?style=flat-square&logo=vuedotjs&logoColor=white)' ;;
Svelte) echo '![Svelte](https://img.shields.io/badge/-Svelte-FF3E00?style=flat-square&logo=svelte&logoColor=white)' ;;
*) echo "![${lang}](https://img.shields.io/badge/-${lang// /%20}-555555?style=flat-square)" ;;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-medium medium

The lang variable is embedded directly into a Markdown image tag without sanitization. If a repository has a maliciously crafted language name (e.g., containing ](...)), it could lead to Markdown injection in the generated README.

Comment on lines +721 to +724
connect="${connect}[![Website](https://img.shields.io/badge/-${blog##*//}-FF5722?style=flat-square&logo=hugo&logoColor=white)](${blog})"$'\n'
fi
if [[ -n "$twitter" ]]; then
connect="${connect}[![X](https://img.shields.io/badge/-@${twitter}-000000?style=flat-square&logo=x&logoColor=white)](https://twitter.com/${twitter})"$'\n'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-medium medium

The blog and twitter fields from the user's GitHub profile are used to construct Markdown links and badges without sanitization. A malicious user could set these fields to javascript: URIs or craft them to break the Markdown structure. While GitHub's own sanitization may prevent XSS, this still allows for Markdown injection and phishing.

Comment on lines +730 to +733
echo "# ${display_name}"
echo ""
if [[ -n "$bio" ]]; then
echo "**${bio}**"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-medium medium

The user's display_name and bio are rendered directly into the README. These fields are user-controlled and can contain arbitrary HTML or Markdown, allowing for profile defacement or injection of malicious content.

Comment on lines +662 to +666
display_name=$(echo "$user_json" | jq -r '.name // empty' 2>/dev/null)
display_name="${display_name:-$gh_user}"
bio=$(echo "$user_json" | jq -r '.bio // empty' 2>/dev/null)
blog=$(echo "$user_json" | jq -r 'select(.blog != null and .blog != "") | .blog' 2>/dev/null)
twitter=$(echo "$user_json" | jq -r 'select(.twitter_username != null and .twitter_username != "") | .twitter_username' 2>/dev/null)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This section has a couple of areas for improvement:

  1. Error Suppression: The jq calls suppress stderr with 2>/dev/null. This can hide important errors and goes against the project's general rules.
  2. Inefficient Parsing: There are four separate jq calls to parse different fields from user_json. This can be consolidated into a single, more efficient call.

Here is a suggested refactoring that addresses both points.

Suggested change
display_name=$(echo "$user_json" | jq -r '.name // empty' 2>/dev/null)
display_name="${display_name:-$gh_user}"
bio=$(echo "$user_json" | jq -r '.bio // empty' 2>/dev/null)
blog=$(echo "$user_json" | jq -r 'select(.blog != null and .blog != "") | .blog' 2>/dev/null)
twitter=$(echo "$user_json" | jq -r 'select(.twitter_username != null and .twitter_username != "") | .twitter_username' 2>/dev/null)
IFS=$'\n' read -r display_name bio blog twitter < <(echo "$user_json" | jq -r '(.name // ""), (.bio // ""), (.blog // ""), (.twitter_username // "")' || printf '\n\n\n\n')
display_name="${display_name:-$gh_user}"
References
  1. Consolidate multiple 'jq' calls into a single pass where possible to improve performance and script efficiency.
  2. In shell scripts with 'set -e' enabled, use '|| true' to prevent the script from exiting when a command like 'jq' fails on an optional lookup. Do not suppress stderr with '2>/dev/null' so that actual syntax or system errors remain visible for debugging.

marcusquinn added a commit that referenced this pull request Mar 9, 2026
- Consolidate 4 separate jq calls for user profile into single pass with
  tab-delimited output, remove stderr suppression (Gemini #6)
- Consolidate own repos loop into single jq pass, eliminating per-row
  jq invocations (Gemini #2)
- Replace sequential N+1 gh api calls for fork parent URLs with parallel
  xargs -P 6 batch fetch (Gemini #1)
- Add _sanitize_md() and _sanitize_url() helpers to sanitize user-controlled
  fields (display_name, bio, blog, twitter) before embedding in markdown,
  preventing markdown injection and javascript: URI attacks (Gemini #4, #5)

Ref: PR #3962 review comments from gemini-code-assist
marcusquinn added a commit that referenced this pull request Mar 9, 2026
…#3963)

* fix: address Gemini code review feedback on profile README generation

- Consolidate 4 separate jq calls for user profile into single pass with
  tab-delimited output, remove stderr suppression (Gemini #6)
- Consolidate own repos loop into single jq pass, eliminating per-row
  jq invocations (Gemini #2)
- Replace sequential N+1 gh api calls for fork parent URLs with parallel
  xargs -P 6 batch fetch (Gemini #1)
- Add _sanitize_md() and _sanitize_url() helpers to sanitize user-controlled
  fields (display_name, bio, blog, twitter) before embedding in markdown,
  preventing markdown injection and javascript: URI attacks (Gemini #4, #5)

Ref: PR #3962 review comments from gemini-code-assist

* fix: address second round of Gemini review feedback

- Remove 2>/dev/null from xargs fork fetch (|| true suffices)
- Tighten _sanitize_url to reject markdown-breaking chars in URLs
  using glob patterns (bash regex [^...] with escaped parens is
  unreliable across bash versions)
- Strip tabs/newlines from jq user profile output to prevent
  tab-delimiter injection in bio/description fields
- Sanitize repo names and descriptions in both own repos (jq gsub)
  and fork repos (_sanitize_md) before markdown embedding
- Keep printf '%s\n' for own_repos (bash $() strips trailing
  newlines, so the explicit \n is needed for section spacing)

Ref: PR #3963 review comments from gemini-code-assist
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Auto-created from TODO.md tag

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant