Skip to content

Docfx/context7 chat#48

Merged
gimlichael merged 5 commits intomainfrom
docfx/context7-chat
Feb 20, 2026
Merged

Docfx/context7 chat#48
gimlichael merged 5 commits intomainfrom
docfx/context7-chat

Conversation

@gimlichael
Copy link
Copy Markdown
Member

@gimlichael gimlichael commented Feb 20, 2026

This pull request introduces a new automated workflow for managing service update propagation across repositories, focusing on dependency updates and release coordination. It adds scripts and workflows to trigger and handle downstream package version bumps, update changelogs, and automate pull request creation, while ensuring only relevant in-house packages are updated. Additionally, a small enhancement is made to the documentation site template.

Automation for Service Update Propagation:

  • Added a new workflow .github/workflows/trigger-downstream.yml to automatically trigger service update events in downstream repositories when a new release is published, using a list of targets from .github/dispatch-targets.json. This workflow securely dispatches events with the relevant version and source repository information. [1] [2]

  • Introduced .github/workflows/service-update.yml to handle incoming service update events or manual triggers. This workflow bumps in-house package dependencies, updates release notes and changelogs, and creates a pull request for the changes. It supports dry-run mode and uses a GitHub App token for authentication.

Dependency Management Improvements:

  • Added .github/scripts/bump-nuget.py, a Python script to selectively update only Codebelt/Cuemon/Savvyio (in-house) package versions in Directory.Packages.props based on trigger source and version, while skipping third-party packages. The script prints a summary of changes and skipped packages.

Documentation Enhancement:

  • Appended a script tag to .docfx/templates/savvyio/layout/_master.tmpl to asynchronously load an external widget for documentation improvements.

Summary by CodeRabbit

  • New Features

    • Added external widget integration to documentation templates.
  • Chores

    • Introduced automated NuGet package version management.
    • Added GitHub Actions workflows for automated service updates and triggering downstream service notifications on releases.
    • Added configuration file for managing dispatch targets.

@gimlichael gimlichael self-assigned this Feb 20, 2026
Copilot AI review requested due to automatic review settings February 20, 2026 17:12
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 20, 2026

📝 Walkthrough

Walkthrough

Introduces automated CI/CD workflows for triggering downstream service updates across repositories, adds a Python script for selective NuGet package version bumping, creates a GitHub dispatch targets configuration file, and integrates an external widget script into the docfx template layout.

Changes

Cohort / File(s) Summary
Documentation Template
.docfx/templates/savvyio/layout/_master.tmpl
Adds external asynchronous script tag loading a widget from context7.com with library reference "/codebeltnet/savvyio" before closing body tag.
GitHub Configuration
.github/dispatch-targets.json
New configuration file defining dispatch targets as a JSON array containing "shared-kernel".
NuGet Update Script
.github/scripts/bump-nuget.py
New Python script that reads environment variables (TRIGGER_SOURCE, TRIGGER_VERSION), maps sources to NuGet package prefixes, parses and updates Directory.Packages.props, and reports changes with error handling.
GitHub Actions Workflows
.github/workflows/service-update.yml, .github/workflows/trigger-downstream.yml
Two new workflows: service-update handles receiving dispatch events to bump packages, update changelogs and release notes, then create PRs; trigger-downstream detects releases, reads dispatch targets, and sends repository_dispatch events to downstream repositories.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

Poem

🐰 Our workflows now dance in GitHub's grand stage,
Automating updates across every page,
Dispatch to dispatch, packages bloom anew,
Service updates flowing through each repository queue,
A widget joins the web, and magic takes flight!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Docfx/context7 chat' is vague and does not clearly convey the main purpose of the pull request. Consider a more descriptive title that captures the primary changes, such as 'Add automated service update workflows and package version management' or 'Add Context7 widget to documentation and implement service update automation'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch docfx/context7-chat

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.

Copy link
Copy Markdown

@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: 6

🧹 Nitpick comments (4)
.github/scripts/bump-nuget.py (2)

25-26: Module-level environment reads make unit testing harder.

TRIGGER_SOURCE and TRIGGER_VERSION are read at import time, meaning tests cannot easily override them without patching os.environ before import or reloading the module. Consider reading them inside main() and passing them as parameters to is_triggered_package. This is minor given the script's CI-only usage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/scripts/bump-nuget.py around lines 25 - 26, The module currently
reads TRIGGER_SOURCE and TRIGGER_VERSION at import time which hinders tests;
move the os.environ.get(...) calls into main() (or a new initializer) and pass
the retrieved values as parameters into is_triggered_package (and any other
functions that currently rely on the module-level
TRIGGER_SOURCE/TRIGGER_VERSION), update is_triggered_package signature to accept
trigger_source and trigger_version, and adjust callers to supply those values so
tests can control environment without reloading the module.

129-129: Useless if-else — both branches return 0.

This was also flagged by Ruff (RUF034). The conditional is dead code. Simplify to a plain return 0 and keep the intent in the comment.

♻️ Proposed fix
-    return 0 if changes else 0  # Return 0 even if no changes (not an error)
+    return 0  # Always succeed — no changes is not an error
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/scripts/bump-nuget.py at line 129, The conditional return "return 0
if changes else 0" is dead code; replace it with a simple "return 0" (keep the
existing comment about returning 0 even if no changes) so the function in
bump-nuget.py no longer contains the useless if-else expression.
.github/workflows/service-update.yml (1)

100-105: dry_run gate is only effective for workflow_dispatch — intended but worth a comment.

For repository_dispatch events, github.event.inputs.dry_run is always empty, so the PR-creation path always runs. If dry-run support is ever needed for dispatched events, it would require adding a dry_run field to client_payload. A brief inline comment would clarify this design choice.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/service-update.yml around lines 100 - 105, Add an inline
comment near the two workflow steps that gate on github.event.inputs.dry_run
(the lines using if: ${{ github.event.inputs.dry_run == 'true' }} and if: ${{
github.event.inputs.dry_run != 'true' }}) explaining that this dry_run input is
only populated for workflow_dispatch events and will be empty for
repository_dispatch events, and note that to support dry-run behavior for
dispatched events you'd need to pass a dry_run field in client_payload of the
repository_dispatch call; keep the comment short and factual for future
maintainers.
.docfx/templates/savvyio/layout/_master.tmpl (1)

137-138: Loading third-party script without integrity checks—explore versioning alternatives with Context7.

Loading JavaScript from an external domain presents a supply chain risk. Context7's official documentation does not provide SRI hashes or versioned URLs (e.g., /widget.vX.Y.Z.js), so adding a self-generated SRI hash would break whenever Context7 updates the script. Instead:

  • Contact Context7 support to request versioned or immutable URLs and published SRI hashes per release.
  • If Context7 cannot provide stability guarantees, consider whether self-hosting a pinned version of the widget is feasible for your documentation workflow.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.docfx/templates/savvyio/layout/_master.tmpl around lines 137 - 138, The
template currently loads the third‑party script directly via the script tag src
"https://context7.com/widget.js" without integrity or versioning; replace this
by either (A) requesting a versioned/immutable URL and published SRI from
Context7 and then updating the script tag to use that versioned URL with an
integrity and crossorigin attribute, or (B) if Context7 cannot provide versioned
releases/SRI, download and self‑host a pinned copy of the widget and change the
script src to the self‑hosted path (and add integrity/crossorigin for the pinned
file); locate the script inclusion in _master.tmpl and update the tag
accordingly after obtaining the versioned file or pinned asset.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/service-update.yml:
- Around line 70-75: The pipeline setting TFM (TFM=$(grep -m1 "^Availability:"
"$f" | sed 's/Availability: //' || echo "...")) never falls back because sed
returns 0; fix by capturing grep output first and then applying the fallback:
run grep -m1 "^Availability:" "$f" into a temp variable (e.g., raw_avail), check
if raw_avail is empty and if so set TFM to the default ".NET 10, .NET 9 and .NET
Standard 2.0", otherwise strip the "Availability: " prefix (using sed or
parameter expansion) and assign that to TFM; update the loop body that
references TFM accordingly.
- Around line 44-51: The CURRENT extraction can be empty which makes awk produce
invalid version strings; update the step that computes CURRENT/NEW/BRANCH to
guard for an empty CURRENT by checking if CURRENT is non-empty and if not either
set CURRENT to a sane default like "0.0.0" or fail early with an error and exit
1; then compute NEW using the same awk expression on the validated CURRENT and
derive BRANCH from NEW (symbols: CURRENT, NEW, BRANCH, the grep/awk pipeline
used to parse CHANGELOG.md). Ensure the chosen behavior writes the correct
values to $GITHUB_OUTPUT (current, new, branch) and stops the workflow or uses
the default when CHANGELOG has no matching entries.
- Around line 37-41: The run step currently injects user-controlled expressions
directly into the shell via "${{ github.event.client_payload.source_repo ||
github.event.inputs.source_repo }}" and "${{
github.event.client_payload.source_version || github.event.inputs.source_version
}}", creating a script-injection risk; fix it by moving those expressions into
environment variables (set SOURCE and VERSION via an env: mapping using the same
${{ ... }} expressions) and then reference the safe env vars inside the run
block when writing to $GITHUB_OUTPUT (use printf/echo with the env vars SOURCE
and VERSION rather than inlining the expressions). Ensure you handle both
fallbacks (client_payload vs inputs) in the env assignments and only use the env
vars inside the run.
- Around line 108-139: The run block directly interpolates step outputs (NEW,
BRANCH, SOURCE, SRC_VER) which can lead to shell injection; change the step to
declare these as step-level env variables (env: NEW: ${{
steps.newver.outputs.new }}, BRANCH: ${{ steps.newver.outputs.branch }}, SOURCE:
${{ steps.trigger.outputs.source }}, SRC_VER: ${{ steps.trigger.outputs.version
}}) and then reference the env vars inside the run script (use "$NEW",
"$BRANCH", "$SOURCE", "$SRC_VER") instead of ${ { steps.*.outputs.* } } inline;
additionally add basic validation/sanitization in the script (e.g., strip
newlines, reject/control dangerous characters or use an allowlist for
BRANCH/NEW) before using values in git/gh commands and ensure all expansions are
quoted, especially for git checkout -b "$BRANCH" and gh pr create arguments.

In @.github/workflows/trigger-downstream.yml:
- Around line 32-35: The workflow currently interpolates user-controlled
github.event.release.tag_name directly into the shell run block (VERSION="${{
github.event.release.tag_name }}"), creating a script-injection risk; change it
to pass the tag through an environment variable (e.g., set RELEASE_TAG: ${{
github.event.release.tag_name }} in the job/env) and then in the run block
reference that safe env var (e.g., use VERSION="${RELEASE_TAG#v}" and/or use
printf '%s' to avoid word-splitting) before writing to $GITHUB_OUTPUT; update
the lines that set and mutate VERSION to use RELEASE_TAG instead of direct
template interpolation.
- Around line 57-74: The dispatch loop currently calls
urllib.request.urlopen(req) inside the for repo in targets loop and the first
HTTPError aborts the whole script; wrap the body of each iteration (the
creation/use of req and the urllib.request.urlopen(req) call and print of
r.status) in a try/except that catches urllib.error.HTTPError and
urllib.error.URLError, log/print a clear failure message including repo and the
exception, continue to the next repo, and record a failure flag/counter so the
script can return a non-zero exit code after the loop if any dispatches failed;
reference the loop variables repo, req, targets and the response r when adding
the try/except and failure tracking.

---

Nitpick comments:
In @.docfx/templates/savvyio/layout/_master.tmpl:
- Around line 137-138: The template currently loads the third‑party script
directly via the script tag src "https://context7.com/widget.js" without
integrity or versioning; replace this by either (A) requesting a
versioned/immutable URL and published SRI from Context7 and then updating the
script tag to use that versioned URL with an integrity and crossorigin
attribute, or (B) if Context7 cannot provide versioned releases/SRI, download
and self‑host a pinned copy of the widget and change the script src to the
self‑hosted path (and add integrity/crossorigin for the pinned file); locate the
script inclusion in _master.tmpl and update the tag accordingly after obtaining
the versioned file or pinned asset.

In @.github/scripts/bump-nuget.py:
- Around line 25-26: The module currently reads TRIGGER_SOURCE and
TRIGGER_VERSION at import time which hinders tests; move the os.environ.get(...)
calls into main() (or a new initializer) and pass the retrieved values as
parameters into is_triggered_package (and any other functions that currently
rely on the module-level TRIGGER_SOURCE/TRIGGER_VERSION), update
is_triggered_package signature to accept trigger_source and trigger_version, and
adjust callers to supply those values so tests can control environment without
reloading the module.
- Line 129: The conditional return "return 0 if changes else 0" is dead code;
replace it with a simple "return 0" (keep the existing comment about returning 0
even if no changes) so the function in bump-nuget.py no longer contains the
useless if-else expression.

In @.github/workflows/service-update.yml:
- Around line 100-105: Add an inline comment near the two workflow steps that
gate on github.event.inputs.dry_run (the lines using if: ${{
github.event.inputs.dry_run == 'true' }} and if: ${{ github.event.inputs.dry_run
!= 'true' }}) explaining that this dry_run input is only populated for
workflow_dispatch events and will be empty for repository_dispatch events, and
note that to support dry-run behavior for dispatched events you'd need to pass a
dry_run field in client_payload of the repository_dispatch call; keep the
comment short and factual for future maintainers.

Comment on lines +37 to +41
run: |
SOURCE="${{ github.event.client_payload.source_repo || github.event.inputs.source_repo }}"
VERSION="${{ github.event.client_payload.source_version || github.event.inputs.source_version }}"
echo "source=$SOURCE" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Script injection risk: client_payload and inputs are user-controlled.

source_repo and source_version are interpolated directly into the shell via ${{ }}. For workflow_dispatch, a user can supply arbitrary strings. Pipe through environment variables to neutralize shell metacharacters.

🛡️ Proposed fix
       - name: Resolve trigger inputs
         id: trigger
         run: |
-          SOURCE="${{ github.event.client_payload.source_repo || github.event.inputs.source_repo }}"
-          VERSION="${{ github.event.client_payload.source_version || github.event.inputs.source_version }}"
+          SOURCE="${DISPATCH_SOURCE:-$INPUT_SOURCE}"
+          VERSION="${DISPATCH_VERSION:-$INPUT_VERSION}"
           echo "source=$SOURCE"   >> $GITHUB_OUTPUT
           echo "version=$VERSION" >> $GITHUB_OUTPUT
+        env:
+          DISPATCH_SOURCE: ${{ github.event.client_payload.source_repo }}
+          DISPATCH_VERSION: ${{ github.event.client_payload.source_version }}
+          INPUT_SOURCE: ${{ github.event.inputs.source_repo }}
+          INPUT_VERSION: ${{ github.event.inputs.source_version }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/service-update.yml around lines 37 - 41, The run step
currently injects user-controlled expressions directly into the shell via "${{
github.event.client_payload.source_repo || github.event.inputs.source_repo }}"
and "${{ github.event.client_payload.source_version ||
github.event.inputs.source_version }}", creating a script-injection risk; fix it
by moving those expressions into environment variables (set SOURCE and VERSION
via an env: mapping using the same ${{ ... }} expressions) and then reference
the safe env vars inside the run block when writing to $GITHUB_OUTPUT (use
printf/echo with the env vars SOURCE and VERSION rather than inlining the
expressions). Ensure you handle both fallbacks (client_payload vs inputs) in the
env assignments and only use the env vars inside the run.

Comment on lines +44 to +51
id: newver
run: |
CURRENT=$(grep -oP '(?<=## \[)[\d.]+(?=\])' CHANGELOG.md | head -1)
NEW=$(echo "$CURRENT" | awk -F. '{printf "%s.%s.%d", $1, $2, $3+1}')
BRANCH="v${NEW}/service-update"
echo "current=$CURRENT" >> $GITHUB_OUTPUT
echo "new=$NEW" >> $GITHUB_OUTPUT
echo "branch=$BRANCH" >> $GITHUB_OUTPUT
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Version parsing produces garbage if CHANGELOG.md has no matching entries.

If grep finds no ## [x.y.z] entry, CURRENT is empty and the awk on Line 47 yields "..1", creating an invalid branch name v..1/service-update. Add a guard.

🐛 Proposed fix
       - name: Determine new version for this repo
         id: newver
         run: |
           CURRENT=$(grep -oP '(?<=## \[)[\d.]+(?=\])' CHANGELOG.md | head -1)
+          if [ -z "$CURRENT" ]; then
+            echo "::error::No version entry found in CHANGELOG.md"
+            exit 1
+          fi
           NEW=$(echo "$CURRENT" | awk -F. '{printf "%s.%s.%d", $1, $2, $3+1}')
           BRANCH="v${NEW}/service-update"
📝 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
id: newver
run: |
CURRENT=$(grep -oP '(?<=## \[)[\d.]+(?=\])' CHANGELOG.md | head -1)
NEW=$(echo "$CURRENT" | awk -F. '{printf "%s.%s.%d", $1, $2, $3+1}')
BRANCH="v${NEW}/service-update"
echo "current=$CURRENT" >> $GITHUB_OUTPUT
echo "new=$NEW" >> $GITHUB_OUTPUT
echo "branch=$BRANCH" >> $GITHUB_OUTPUT
id: newver
run: |
CURRENT=$(grep -oP '(?<=## \[)[\d.]+(?=\])' CHANGELOG.md | head -1)
if [ -z "$CURRENT" ]; then
echo "::error::No version entry found in CHANGELOG.md"
exit 1
fi
NEW=$(echo "$CURRENT" | awk -F. '{printf "%s.%s.%d", $1, $2, $3+1}')
BRANCH="v${NEW}/service-update"
echo "current=$CURRENT" >> $GITHUB_OUTPUT
echo "new=$NEW" >> $GITHUB_OUTPUT
echo "branch=$BRANCH" >> $GITHUB_OUTPUT
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/service-update.yml around lines 44 - 51, The CURRENT
extraction can be empty which makes awk produce invalid version strings; update
the step that computes CURRENT/NEW/BRANCH to guard for an empty CURRENT by
checking if CURRENT is non-empty and if not either set CURRENT to a sane default
like "0.0.0" or fail early with an error and exit 1; then compute NEW using the
same awk expression on the validated CURRENT and derive BRANCH from NEW
(symbols: CURRENT, NEW, BRANCH, the grep/awk pipeline used to parse
CHANGELOG.md). Ensure the chosen behavior writes the correct values to
$GITHUB_OUTPUT (current, new, branch) and stops the workflow or uses the default
when CHANGELOG has no matching entries.

Comment on lines +70 to +75
for f in .nuget/*/PackageReleaseNotes.txt; do
[ -f "$f" ] || continue
TFM=$(grep -m1 "^Availability:" "$f" | sed 's/Availability: //' || echo ".NET 10, .NET 9 and .NET Standard 2.0")
ENTRY="Version: ${NEW}\nAvailability: ${TFM}\n \n# ALM\n- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)\n \n"
{ printf "$ENTRY"; cat "$f"; } > "$f.tmp" && mv "$f.tmp" "$f"
done
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

TFM fallback never triggers — sed exits 0 even when grep finds no match.

On Line 72, grep -m1 … | sed … forms a pipeline whose exit status is that of sed, which is 0 even on empty input. The || echo "…" default is never reached, so TFM will be empty when the file has no Availability: line.

Use a separate assignment or grep alone with a conditional:

🐛 Proposed fix
-            TFM=$(grep -m1 "^Availability:" "$f" | sed 's/Availability: //' || echo ".NET 10, .NET 9 and .NET Standard 2.0")
+            TFM=$(grep -m1 "^Availability:" "$f" | sed 's/Availability: //')
+            TFM="${TFM:-.NET 10, .NET 9 and .NET Standard 2.0}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/service-update.yml around lines 70 - 75, The pipeline
setting TFM (TFM=$(grep -m1 "^Availability:" "$f" | sed 's/Availability: //' ||
echo "...")) never falls back because sed returns 0; fix by capturing grep
output first and then applying the fallback: run grep -m1 "^Availability:" "$f"
into a temp variable (e.g., raw_avail), check if raw_avail is empty and if so
set TFM to the default ".NET 10, .NET 9 and .NET Standard 2.0", otherwise strip
the "Availability: " prefix (using sed or parameter expansion) and assign that
to TFM; update the loop body that references TFM accordingly.

Comment on lines +108 to +139
run: |
NEW="${{ steps.newver.outputs.new }}"
BRANCH="${{ steps.newver.outputs.branch }}"
SOURCE="${{ steps.trigger.outputs.source }}"
SRC_VER="${{ steps.trigger.outputs.version }}"

git config user.name "codebelt-aicia[bot]"
git config user.email "codebelt-aicia[bot]@users.noreply.github.com"
git checkout -b "$BRANCH"
git add -A
git diff --cached --quiet && echo "Nothing changed - skipping PR." && exit 0
git commit -m "V${NEW}/service update"
git push origin "$BRANCH"

echo "This is a service update that focuses on package dependencies." > pr_body.txt
echo "" >> pr_body.txt
echo "Automated changes:" >> pr_body.txt
echo "- Codebelt/Cuemon package versions bumped to latest compatible" >> pr_body.txt
echo "- PackageReleaseNotes.txt updated for v${NEW}" >> pr_body.txt
echo "- CHANGELOG.md entry added for v${NEW}" >> pr_body.txt
echo "" >> pr_body.txt
echo "Note: Third-party packages (Microsoft.Extensions.*, BenchmarkDotNet, etc.) are not auto-updated." >> pr_body.txt
echo "Use Dependabot or manual updates for those." >> pr_body.txt
echo "" >> pr_body.txt
echo "Generated by codebelt-aicia" >> pr_body.txt
if [ -n "$SOURCE" ] && [ -n "$SRC_VER" ]; then
echo "Triggered by: ${SOURCE} @ ${SRC_VER}" >> pr_body.txt
else
echo "Triggered by: manual workflow dispatch" >> pr_body.txt
fi

gh pr create --title "V${NEW}/service update" --body-file pr_body.txt --base main --head "$BRANCH" --assignee gimlichael
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Steps that interpolate steps.*.outputs.* into run: blocks are also injection vectors.

Lines 109–112 interpolate outputs that ultimately derive from user-controlled input (source_repo, source_version, and the tag parsed from CHANGELOG.md). The same env-var pattern should be applied here to prevent injection through crafted version strings or repo names that flow into shell commands and gh pr create.

🛡️ Proposed fix (partial — apply env-var indirection for all interpolated outputs)
       - name: Create branch and open PR
         if: ${{ github.event.inputs.dry_run != 'true' }}
         env:
           GH_TOKEN: ${{ steps.app-token.outputs.token }}
+          NEW: ${{ steps.newver.outputs.new }}
+          BRANCH: ${{ steps.newver.outputs.branch }}
+          SOURCE: ${{ steps.trigger.outputs.source }}
+          SRC_VER: ${{ steps.trigger.outputs.version }}
         run: |
-          NEW="${{ steps.newver.outputs.new }}"
-          BRANCH="${{ steps.newver.outputs.branch }}"
-          SOURCE="${{ steps.trigger.outputs.source }}"
-          SRC_VER="${{ steps.trigger.outputs.version }}"
-
           git config user.name  "codebelt-aicia[bot]"
📝 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
run: |
NEW="${{ steps.newver.outputs.new }}"
BRANCH="${{ steps.newver.outputs.branch }}"
SOURCE="${{ steps.trigger.outputs.source }}"
SRC_VER="${{ steps.trigger.outputs.version }}"
git config user.name "codebelt-aicia[bot]"
git config user.email "codebelt-aicia[bot]@users.noreply.github.com"
git checkout -b "$BRANCH"
git add -A
git diff --cached --quiet && echo "Nothing changed - skipping PR." && exit 0
git commit -m "V${NEW}/service update"
git push origin "$BRANCH"
echo "This is a service update that focuses on package dependencies." > pr_body.txt
echo "" >> pr_body.txt
echo "Automated changes:" >> pr_body.txt
echo "- Codebelt/Cuemon package versions bumped to latest compatible" >> pr_body.txt
echo "- PackageReleaseNotes.txt updated for v${NEW}" >> pr_body.txt
echo "- CHANGELOG.md entry added for v${NEW}" >> pr_body.txt
echo "" >> pr_body.txt
echo "Note: Third-party packages (Microsoft.Extensions.*, BenchmarkDotNet, etc.) are not auto-updated." >> pr_body.txt
echo "Use Dependabot or manual updates for those." >> pr_body.txt
echo "" >> pr_body.txt
echo "Generated by codebelt-aicia" >> pr_body.txt
if [ -n "$SOURCE" ] && [ -n "$SRC_VER" ]; then
echo "Triggered by: ${SOURCE} @ ${SRC_VER}" >> pr_body.txt
else
echo "Triggered by: manual workflow dispatch" >> pr_body.txt
fi
gh pr create --title "V${NEW}/service update" --body-file pr_body.txt --base main --head "$BRANCH" --assignee gimlichael
- name: Create branch and open PR
if: ${{ github.event.inputs.dry_run != 'true' }}
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
NEW: ${{ steps.newver.outputs.new }}
BRANCH: ${{ steps.newver.outputs.branch }}
SOURCE: ${{ steps.trigger.outputs.source }}
SRC_VER: ${{ steps.trigger.outputs.version }}
run: |
git config user.name "codebelt-aicia[bot]"
git config user.email "codebelt-aicia[bot]@users.noreply.github.com"
git checkout -b "$BRANCH"
git add -A
git diff --cached --quiet && echo "Nothing changed - skipping PR." && exit 0
git commit -m "V${NEW}/service update"
git push origin "$BRANCH"
echo "This is a service update that focuses on package dependencies." > pr_body.txt
echo "" >> pr_body.txt
echo "Automated changes:" >> pr_body.txt
echo "- Codebelt/Cuemon package versions bumped to latest compatible" >> pr_body.txt
echo "- PackageReleaseNotes.txt updated for v${NEW}" >> pr_body.txt
echo "- CHANGELOG.md entry added for v${NEW}" >> pr_body.txt
echo "" >> pr_body.txt
echo "Note: Third-party packages (Microsoft.Extensions.*, BenchmarkDotNet, etc.) are not auto-updated." >> pr_body.txt
echo "Use Dependabot or manual updates for those." >> pr_body.txt
echo "" >> pr_body.txt
echo "Generated by codebelt-aicia" >> pr_body.txt
if [ -n "$SOURCE" ] && [ -n "$SRC_VER" ]; then
echo "Triggered by: ${SOURCE} @ ${SRC_VER}" >> pr_body.txt
else
echo "Triggered by: manual workflow dispatch" >> pr_body.txt
fi
gh pr create --title "V${NEW}/service update" --body-file pr_body.txt --base main --head "$BRANCH" --assignee gimlichael
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/service-update.yml around lines 108 - 139, The run block
directly interpolates step outputs (NEW, BRANCH, SOURCE, SRC_VER) which can lead
to shell injection; change the step to declare these as step-level env variables
(env: NEW: ${{ steps.newver.outputs.new }}, BRANCH: ${{
steps.newver.outputs.branch }}, SOURCE: ${{ steps.trigger.outputs.source }},
SRC_VER: ${{ steps.trigger.outputs.version }}) and then reference the env vars
inside the run script (use "$NEW", "$BRANCH", "$SOURCE", "$SRC_VER") instead of
${ { steps.*.outputs.* } } inline; additionally add basic
validation/sanitization in the script (e.g., strip newlines, reject/control
dangerous characters or use an allowlist for BRANCH/NEW) before using values in
git/gh commands and ensure all expansions are quoted, especially for git
checkout -b "$BRANCH" and gh pr create arguments.

Comment on lines +32 to +35
run: |
VERSION="${{ github.event.release.tag_name }}"
VERSION="${VERSION#v}"
echo "version=$VERSION" >> $GITHUB_OUTPUT
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Script injection risk: github.event.release.tag_name is user-controlled input.

Interpolating ${{ github.event.release.tag_name }} directly into a run: shell block allows arbitrary command execution if the tag contains shell metacharacters. Use an environment variable instead.

🛡️ Proposed fix
       - name: Extract version from release tag
         if: steps.check.outputs.has_targets == 'true'
         id: version
         run: |
-          VERSION="${{ github.event.release.tag_name }}"
+          VERSION="${TAG_NAME}"
           VERSION="${VERSION#v}"
           echo "version=$VERSION" >> $GITHUB_OUTPUT
+        env:
+          TAG_NAME: ${{ github.event.release.tag_name }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/trigger-downstream.yml around lines 32 - 35, The workflow
currently interpolates user-controlled github.event.release.tag_name directly
into the shell run block (VERSION="${{ github.event.release.tag_name }}"),
creating a script-injection risk; change it to pass the tag through an
environment variable (e.g., set RELEASE_TAG: ${{ github.event.release.tag_name
}} in the job/env) and then in the run block reference that safe env var (e.g.,
use VERSION="${RELEASE_TAG#v}" and/or use printf '%s' to avoid word-splitting)
before writing to $GITHUB_OUTPUT; update the lines that set and mutate VERSION
to use RELEASE_TAG instead of direct template interpolation.

Comment on lines +57 to +74
for repo in targets:
url = f'https://api.github.com/repos/codebeltnet/{repo}/dispatches'
payload = json.dumps({
'event_type': 'codebelt-service-update',
'client_payload': {
'source_repo': source,
'source_version': version
}
}).encode()
req = urllib.request.Request(url, data=payload, method='POST', headers={
'Authorization': f'Bearer {token}',
'Accept': 'application/vnd.github+json',
'Content-Type': 'application/json',
'X-GitHub-Api-Version': '2022-11-28'
})
with urllib.request.urlopen(req) as r:
print(f'✓ Dispatched to {repo}: HTTP {r.status}')
EOF
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Dispatch loop aborts on first HTTP error — remaining targets are skipped.

urllib.request.urlopen raises urllib.error.HTTPError on non-2xx responses. If one target repo fails (e.g., 404 for a deleted repo), the remaining repos never receive their dispatch. Wrap each iteration in a try/except and track failures.

🔧 Proposed fix
+          failed = []
           for repo in targets:
-              url     = f'https://api.github.com/repos/codebeltnet/{repo}/dispatches'
-              payload = json.dumps({
-                  'event_type': 'codebelt-service-update',
-                  'client_payload': {
-                      'source_repo':    source,
-                      'source_version': version
-                  }
-              }).encode()
-              req = urllib.request.Request(url, data=payload, method='POST', headers={
-                  'Authorization':  f'Bearer {token}',
-                  'Accept':         'application/vnd.github+json',
-                  'Content-Type':   'application/json',
-                  'X-GitHub-Api-Version': '2022-11-28'
-              })
-              with urllib.request.urlopen(req) as r:
-                  print(f'✓ Dispatched to {repo}: HTTP {r.status}')
+              try:
+                  url     = f'https://api.github.com/repos/codebeltnet/{repo}/dispatches'
+                  payload = json.dumps({
+                      'event_type': 'codebelt-service-update',
+                      'client_payload': {
+                          'source_repo':    source,
+                          'source_version': version
+                      }
+                  }).encode()
+                  req = urllib.request.Request(url, data=payload, method='POST', headers={
+                      'Authorization':  f'Bearer {token}',
+                      'Accept':         'application/vnd.github+json',
+                      'Content-Type':   'application/json',
+                      'X-GitHub-Api-Version': '2022-11-28'
+                  })
+                  with urllib.request.urlopen(req) as r:
+                      print(f'✓ Dispatched to {repo}: HTTP {r.status}')
+              except Exception as e:
+                  print(f'✗ Failed to dispatch to {repo}: {e}')
+                  failed.append(repo)
+
+          if failed:
+              print(f'::error::Failed to dispatch to: {", ".join(failed)}')
+              sys.exit(1)
📝 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
for repo in targets:
url = f'https://api.github.com/repos/codebeltnet/{repo}/dispatches'
payload = json.dumps({
'event_type': 'codebelt-service-update',
'client_payload': {
'source_repo': source,
'source_version': version
}
}).encode()
req = urllib.request.Request(url, data=payload, method='POST', headers={
'Authorization': f'Bearer {token}',
'Accept': 'application/vnd.github+json',
'Content-Type': 'application/json',
'X-GitHub-Api-Version': '2022-11-28'
})
with urllib.request.urlopen(req) as r:
print(f'✓ Dispatched to {repo}: HTTP {r.status}')
EOF
failed = []
for repo in targets:
try:
url = f'https://api.github.com/repos/codebeltnet/{repo}/dispatches'
payload = json.dumps({
'event_type': 'codebelt-service-update',
'client_payload': {
'source_repo': source,
'source_version': version
}
}).encode()
req = urllib.request.Request(url, data=payload, method='POST', headers={
'Authorization': f'Bearer {token}',
'Accept': 'application/vnd.github+json',
'Content-Type': 'application/json',
'X-GitHub-Api-Version': '2022-11-28'
})
with urllib.request.urlopen(req) as r:
print(f'✓ Dispatched to {repo}: HTTP {r.status}')
except Exception as e:
print(f'✗ Failed to dispatch to {repo}: {e}')
failed.append(repo)
if failed:
print(f'::error::Failed to dispatch to: {", ".join(failed)}')
sys.exit(1)
EOF
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/trigger-downstream.yml around lines 57 - 74, The dispatch
loop currently calls urllib.request.urlopen(req) inside the for repo in targets
loop and the first HTTPError aborts the whole script; wrap the body of each
iteration (the creation/use of req and the urllib.request.urlopen(req) call and
print of r.status) in a try/except that catches urllib.error.HTTPError and
urllib.error.URLError, log/print a clear failure message including repo and the
exception, continue to the next repo, and record a failure flag/counter so the
script can return a non-zero exit code after the loop if any dispatches failed;
reference the loop variables repo, req, targets and the response r when adding
the try/except and failure tracking.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds GitHub Actions–based automation to propagate “service update” dependency bumps across Codebelt repos, plus a small DocFX template change for the documentation site.

Changes:

  • Introduces a release-triggered workflow to dispatch repository_dispatch events to downstream repositories based on .github/dispatch-targets.json.
  • Adds a downstream handler workflow that bumps in-house NuGet package versions, updates release notes/changelog, and opens an automated PR.
  • Adds a Python helper to selectively bump package versions in Directory.Packages.props, plus an external widget script to the DocFX template.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
.github/workflows/trigger-downstream.yml Dispatches a service-update event to downstream repos on non-prerelease releases.
.github/workflows/service-update.yml Receives dispatch/manual triggers, bumps deps + release notes/changelog, and opens a PR.
.github/scripts/bump-nuget.py Updates Directory.Packages.props package versions based on trigger source/version.
.github/dispatch-targets.json Defines downstream repo targets for dispatch.
.docfx/templates/savvyio/layout/_master.tmpl Loads an external Context7 widget script on documentation pages.

Comment on lines +14 to +15
required: false
default: ''
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

source_version is optional/default empty, but bump-nuget.py requires TRIGGER_VERSION and will fail the workflow if it’s not provided. Either mark this input required for manual dispatch, or guard the bump step when the resolved version is empty.

Suggested change
required: false
default: ''
required: true

Copilot uses AI. Check for mistakes.
Comment on lines +125 to +130
echo "- Codebelt/Cuemon package versions bumped to latest compatible" >> pr_body.txt
echo "- PackageReleaseNotes.txt updated for v${NEW}" >> pr_body.txt
echo "- CHANGELOG.md entry added for v${NEW}" >> pr_body.txt
echo "" >> pr_body.txt
echo "Note: Third-party packages (Microsoft.Extensions.*, BenchmarkDotNet, etc.) are not auto-updated." >> pr_body.txt
echo "Use Dependabot or manual updates for those." >> pr_body.txt
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The PR body claims "Codebelt/Cuemon package versions" are bumped and that only third-party packages are skipped, but bump-nuget.py only bumps packages belonging to the triggering source repo’s prefixes. Consider adjusting the wording to reflect "packages from ${SOURCE}" (and avoid calling other in-house packages "third-party").

Suggested change
echo "- Codebelt/Cuemon package versions bumped to latest compatible" >> pr_body.txt
echo "- PackageReleaseNotes.txt updated for v${NEW}" >> pr_body.txt
echo "- CHANGELOG.md entry added for v${NEW}" >> pr_body.txt
echo "" >> pr_body.txt
echo "Note: Third-party packages (Microsoft.Extensions.*, BenchmarkDotNet, etc.) are not auto-updated." >> pr_body.txt
echo "Use Dependabot or manual updates for those." >> pr_body.txt
echo "- Package versions from the triggering source repository bumped to latest compatible" >> pr_body.txt
echo "- PackageReleaseNotes.txt updated for v${NEW}" >> pr_body.txt
echo "- CHANGELOG.md entry added for v${NEW}" >> pr_body.txt
echo "" >> pr_body.txt
echo "Note: Only packages whose IDs match the triggering source repository's prefixes are auto-updated." >> pr_body.txt
echo "Other package families (e.g., Microsoft.Extensions.*, BenchmarkDotNet) are not auto-updated by this workflow; use Dependabot or manual updates for those." >> pr_body.txt

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +7
Only updates packages published by the triggering source repo.
Does NOT update Microsoft.Extensions.*, BenchmarkDotNet, or other third-party packages.
Does NOT parse TFM conditions - only bumps Codebelt/Cuemon/Savvyio packages to the triggering version.
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

Docstring says it "only bumps Codebelt/Cuemon/Savvyio packages", but the implementation bumps only packages whose IDs match the triggering repo’s configured prefixes (SOURCE_PACKAGE_MAP). Update the docstring (or broaden the implementation) so documentation matches behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +119 to +123
if skipped_third_party:
print()
print(f"Skipped {len(skipped_third_party)} third-party package(s):")
print("\n".join(skipped_third_party[:5])) # Show first 5
if len(skipped_third_party) > 5:
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The script prints "Skipped … third-party package(s)" for every non-matching package, but non-matching packages can still be in-house (just owned by a different source repo). Consider renaming the variable/message to something like "skipped (not from trigger source)" and reserve "third-party" for an explicit classification.

Copilot uses AI. Check for mistakes.
import sys
from typing import Dict, List

TRIGGER_SOURCE = os.environ.get("TRIGGER_SOURCE", "")
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

TRIGGER_SOURCE is used as a case-sensitive key into SOURCE_PACKAGE_MAP. If the dispatch payload uses different casing, no packages will be updated (but the workflow can still open a PR). Normalize TRIGGER_SOURCE (e.g., .strip().lower()) before looking it up.

Suggested change
TRIGGER_SOURCE = os.environ.get("TRIGGER_SOURCE", "")
TRIGGER_SOURCE = os.environ.get("TRIGGER_SOURCE", "").strip().lower()

Copilot uses AI. Check for mistakes.
</div>
</footer>

<script async src="https://context7.com/widget.js" data-library="/codebeltnet/savvyio"></script>
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

This template now loads third-party JS from https://context7.com/widget.js on every documentation page. Because this executes with full page privileges, consider making it opt-in via DocFX metadata/config (so it can be disabled) and documenting security/privacy implications (and any CSP strategy) to reduce supply-chain risk.

Suggested change
<script async src="https://context7.com/widget.js" data-library="/codebeltnet/savvyio"></script>
{{^_disableContext7Script}}
<script async src="https://context7.com/widget.js" data-library="{{_context7Library}}{{^_context7Library}}/codebeltnet/savvyio{{/_context7Library}}"></script>
{{/_disableContext7Script}}

Copilot uses AI. Check for mistakes.
Comment on lines +53 to +57
- name: Generate codebelt-aicia token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ vars.CODEBELT_AICIA_APP_ID }}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

A GitHub App token is generated, but later git push origin "$BRANCH" will still authenticate using the credentials from actions/checkout (defaults to GITHUB_TOKEN). If the intent is that the App identity owns the branch/commits (or if GITHUB_TOKEN push is restricted), configure checkout/remote so git operations use steps.app-token.outputs.token too.

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +14
required: false
default: ''
source_version:
description: 'Version released by source (e.g. 10.3.0)'
required: false
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

source_repo is optional/default empty, but later the workflow always runs bump-nuget.py, which exits non-zero when either trigger variable is missing. Consider making this input required for workflow_dispatch, or add an early validation/skip path (especially for dry_run).

Suggested change
required: false
default: ''
source_version:
description: 'Version released by source (e.g. 10.3.0)'
required: false
required: true
default: ''
source_version:
description: 'Version released by source (e.g. 10.3.0)'
required: true

Copilot uses AI. Check for mistakes.
@codecov
Copy link
Copy Markdown

codecov bot commented Feb 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.11%. Comparing base (54d4374) to head (6106d81).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main      #48   +/-   ##
=======================================
  Coverage   79.11%   79.11%           
=======================================
  Files         177      177           
  Lines        3711     3711           
  Branches      365      365           
=======================================
  Hits         2936     2936           
  Misses        774      774           
  Partials        1        1           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@gimlichael gimlichael merged commit 50b4fbb into main Feb 20, 2026
276 of 277 checks passed
@gimlichael gimlichael deleted the docfx/context7-chat branch February 20, 2026 19:26
@coderabbitai coderabbitai bot mentioned this pull request Feb 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants