Skip to content

Commit 76a3c9e

Browse files
committed
feat: /codeboarding comment command to trigger on-demand (issue_comment)
Guard now resolves the PR from either a pull_request event or an issue_comment '/codeboarding' command (comment body read from env, never interpolated -> no injection; SHAs fetched via gh api). Reacts 👀 to acknowledge. Configurable via trigger_command. Sticky comment + base_ref now use the resolved PR explicitly.
1 parent 6de6045 commit 76a3c9e

2 files changed

Lines changed: 69 additions & 11 deletions

File tree

README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,21 @@ name: Architecture diff
2525
on:
2626
pull_request:
2727
types: [opened, synchronize, reopened, ready_for_review]
28+
issue_comment: # enables the /codeboarding command on PRs
29+
types: [created]
2830

2931
permissions:
30-
pull-requests: write # the only permission needed — nothing is pushed
32+
pull-requests: write # the only permission needed — nothing is pushed
3133

3234
jobs:
3335
diagram:
3436
runs-on: ubuntu-latest
35-
if: github.event.pull_request.draft == false
37+
# Run on (non-draft) PR events, OR when someone comments "/codeboarding" on a PR.
38+
# The if-gate is important: without it a runner spins up for every comment.
39+
if: >
40+
(github.event_name == 'pull_request' && github.event.pull_request.draft == false) ||
41+
(github.event_name == 'issue_comment' && github.event.issue.pull_request != null &&
42+
startsWith(github.event.comment.body, '/codeboarding'))
3643
timeout-minutes: 60
3744
steps:
3845
- uses: codeboarding/codeboarding-action@v1
@@ -42,6 +49,12 @@ jobs:
4249
4350
You need **one secret**: an LLM API key. OpenRouter is the default; pass your own model via the `agent_model` / `parsing_model` inputs if you prefer.
4451

52+
### On-demand: the `/codeboarding` command
53+
54+
Comment **`/codeboarding`** on any pull request to (re)run the diagram on demand — handy after the engine/baseline changes, or on draft PRs you don't auto-review. The action reacts with 👀 to acknowledge. Change the word via the `trigger_command` input.
55+
56+
> **Note:** GitHub runs `issue_comment` workflows from the **default branch's** copy of the workflow file. So the command only works once this workflow is merged to your default branch — a workflow that exists only on a feature branch won't respond to comments.
57+
4558
## Inputs
4659

4760
| Input | Default | Description |
@@ -57,6 +70,7 @@ You need **one secret**: an LLM API key. OpenRouter is the default; pass your ow
5770
| `changed_only` | `false` | Draw only changed components and their incident edges. |
5871
| `render_depth` | `1` | Component levels to **draw** in the PR diagram, independent of `depth_level`: `1` = top-level flat, `2` = +one nesting level as subgraphs. Analyze deep, display shallow. |
5972
| `cta_base_url` | `''` | Base URL of a click proxy. When set, the comment adds "explore in browser" / "open in VS Code or Cursor" / "get the extension" links (with `owner`/`repo`/`pr` appended). Empty disables the CTA. |
73+
| `trigger_command` | `/codeboarding` | PR-comment slash-command that triggers an on-demand run (requires the `issue_comment` trigger in your workflow). |
6074

6175
## Outputs
6276

action.yml

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ inputs:
5050
description: 'Base URL of the click proxy (e.g. https://go.codeboarding.org). When set, the comment adds "open in workspace" / "get the extension" links with owner/repo/pr appended. Empty disables the CTA.'
5151
required: false
5252
default: ''
53+
trigger_command:
54+
description: 'Slash-command that triggers the action from a PR comment (issue_comment event). A comment whose first word is this runs the diagram on-demand.'
55+
required: false
56+
default: '/codeboarding'
5357

5458
outputs:
5559
diagram_md:
@@ -65,20 +69,59 @@ outputs:
6569
runs:
6670
using: 'composite'
6771
steps:
68-
- name: Guard — PR event only
72+
- name: Guard — resolve the target PR
6973
id: guard
7074
shell: bash
75+
env:
76+
GH_TOKEN: ${{ inputs.github_token }}
77+
# Read from env, NEVER interpolated into the script — a comment body is
78+
# untrusted input and must not reach the shell as code (injection).
79+
COMMENT_BODY: ${{ github.event.comment.body }}
80+
TRIGGER: ${{ inputs.trigger_command }}
81+
EVENT: ${{ github.event_name }}
7182
run: |
72-
if [ -z "${{ github.event.pull_request.number }}" ]; then
73-
echo "::warning::CodeBoarding Architecture Diff only runs on pull_request events. Skipping."
74-
echo "skip=true" >> $GITHUB_OUTPUT
83+
skip() { echo "::notice::$1 Skipping."; echo "skip=true" >> $GITHUB_OUTPUT; exit 0; }
84+
85+
if [ "$EVENT" = "pull_request" ] || [ "$EVENT" = "pull_request_target" ]; then
86+
PR_NUMBER="${{ github.event.pull_request.number }}"
87+
BASE_SHA="${{ github.event.pull_request.base.sha }}"
88+
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
89+
BASE_REF="${{ github.event.pull_request.base.ref }}"
90+
elif [ "$EVENT" = "issue_comment" ]; then
91+
# On-demand "/codeboarding" command. Must be a PR comment whose first
92+
# word is the trigger; the payload lacks SHAs so we query the API.
93+
[ -n "${{ github.event.issue.pull_request.url }}" ] || skip "Comment is on a plain issue, not a PR."
94+
FIRST_WORD="$(printf '%s' "$COMMENT_BODY" | tr -d '\r' | awk 'NR==1{print $1; exit}')"
95+
[ "$FIRST_WORD" = "$TRIGGER" ] || skip "Comment does not start with '$TRIGGER'."
96+
PR_NUMBER="${{ github.event.issue.number }}"
97+
PR_JSON="$(gh api "repos/${{ github.repository }}/pulls/${PR_NUMBER}")"
98+
BASE_SHA="$(printf '%s' "$PR_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["base"]["sha"])')"
99+
HEAD_SHA="$(printf '%s' "$PR_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["head"]["sha"])')"
100+
BASE_REF="$(printf '%s' "$PR_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["base"]["ref"])')"
75101
else
76-
echo "skip=false" >> $GITHUB_OUTPUT
77-
echo "base_sha=${{ github.event.pull_request.base.sha }}" >> $GITHUB_OUTPUT
78-
echo "head_sha=${{ github.event.pull_request.head.sha }}" >> $GITHUB_OUTPUT
79-
echo "pr_number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
102+
skip "Unsupported event '$EVENT' (use pull_request or issue_comment)."
80103
fi
81104
105+
[ -n "$PR_NUMBER" ] || skip "No pull request in context."
106+
{
107+
echo "skip=false"
108+
echo "pr_number=$PR_NUMBER"
109+
echo "base_sha=$BASE_SHA"
110+
echo "head_sha=$HEAD_SHA"
111+
echo "base_ref=$BASE_REF"
112+
} >> $GITHUB_OUTPUT
113+
echo "Resolved PR #$PR_NUMBER (base=$BASE_SHA head=$HEAD_SHA) via $EVENT"
114+
115+
- name: Acknowledge command
116+
if: steps.guard.outputs.skip != 'true' && github.event_name == 'issue_comment'
117+
shell: bash
118+
env:
119+
GH_TOKEN: ${{ inputs.github_token }}
120+
run: |
121+
# 👀 react to the triggering comment so the user knows it was picked up.
122+
gh api -X POST "repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions" \
123+
-f content=eyes >/dev/null 2>&1 || true
124+
82125
- name: Checkout CodeBoarding engine
83126
if: steps.guard.outputs.skip != 'true'
84127
uses: actions/checkout@v4
@@ -379,7 +422,7 @@ runs:
379422
shell: bash
380423
run: |
381424
HEADER="${{ inputs.comment_header }}"
382-
BASE_REF="${{ github.event.pull_request.base.ref }}"
425+
BASE_REF="${{ steps.guard.outputs.base_ref }}"
383426
N="${{ steps.diagram.outputs.n_changed }}"
384427
RENDERED="${{ steps.diagram.outputs.rendered }}"
385428
TRUNC="${{ steps.diagram.outputs.truncated }}"
@@ -440,5 +483,6 @@ runs:
440483
uses: marocchino/sticky-pull-request-comment@v2
441484
with:
442485
header: codeboarding-architecture-diff
486+
number: ${{ steps.guard.outputs.pr_number }}
443487
path: ${{ steps.body.outputs.body_file }}
444488
GITHUB_TOKEN: ${{ inputs.github_token }}

0 commit comments

Comments
 (0)