From f20517f243e88a355adc39639176e31cda2aeb57 Mon Sep 17 00:00:00 2001 From: Svilen Stefanov Date: Thu, 11 Jun 2026 17:26:40 +0200 Subject: [PATCH 1/4] Link GitHub action with CodeBoarding-webview --- README.md | 2 + action.yml | 86 ++++++++++++++++++++++++++++++++++++++++- scripts/build_cta.py | 61 +++++++++++++++++++++++++++-- tests/test_build_cta.py | 46 ++++++++++++++++++++++ 4 files changed, 189 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6063451..0927c61 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,8 @@ The command needs the `issue_comment` trigger and runs from your default branch | `comment_header` | `Architecture review` | Heading for the PR comment. | | `trigger_command` | `/codeboarding` | Slash command for trusted on-demand runs. | | `cta_base_url` | empty | Click-proxy base URL: deep-links the editor link into VS Code/Cursor and adds a "get the extension" link (tracks owner/repo/pr). Empty links to the extension listing instead (GitHub strips `vscode:`/`cursor:` from comments). | +| `webview_base_url` | `https://app.codeboarding.org` | Hosted webview base URL. The PR comment adds an "explore in browser" link to this PR's head-vs-base diff. Needs `commit_head_analysis` (same-repo PRs only); omitted on forks. Set empty to disable. | +| `commit_head_analysis` | `true` | Commit the generated head `.codeboarding/analysis.json` (+ health report) to the PR branch so the webview can read it at the head SHA. Same-repo PRs only (the token is read-only on forks). | ## Outputs diff --git a/action.yml b/action.yml index 34349fa..be6427a 100644 --- a/action.yml +++ b/action.yml @@ -54,6 +54,14 @@ inputs: description: 'Base URL of the click proxy (e.g. https://go.codeboarding.org). When set, the editor link deep-links into VS Code/Cursor via the proxy and a "get the extension" link is added (owner/repo/pr tracked). Empty (default) links to the extension listing instead, since GitHub strips vscode:/cursor: schemes from comment links.' required: false default: '' + webview_base_url: + description: 'Base URL of the hosted webview (default https://app.codeboarding.org). The PR comment adds an "explore in browser" link deep-linking to this PR''s head-vs-base architecture diff. Requires the head analysis.json to be committed to the PR branch (commit_head_analysis), so it is omitted on fork PRs. Set empty to disable the webview link.' + required: false + default: 'https://app.codeboarding.org' + commit_head_analysis: + description: 'Commit the generated head .codeboarding/analysis.json (+ health report) back to the PR branch so the webview can fetch it at the head SHA. Same-repo PRs only (the token is read-only on forks). Required for the webview "explore in browser" link.' + required: false + default: 'true' trigger_command: 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.' required: false @@ -88,6 +96,7 @@ runs: PR_NUMBER_PULL: ${{ github.event.pull_request.number }} PULL_BASE_SHA: ${{ github.event.pull_request.base.sha }} PULL_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + PULL_HEAD_REF: ${{ github.event.pull_request.head.ref }} PULL_BASE_REF: ${{ github.event.pull_request.base.ref }} PULL_BASE_REPO: ${{ github.event.pull_request.base.repo.full_name }} PULL_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} @@ -115,6 +124,7 @@ runs: PR_NUMBER="$PR_NUMBER_PULL" BASE_SHA="$PULL_BASE_SHA" HEAD_SHA="$PULL_HEAD_SHA" + HEAD_REF="$PULL_HEAD_REF" BASE_REF="$PULL_BASE_REF" BASE_REPO="$PULL_BASE_REPO" HEAD_REPO="$PULL_HEAD_REPO" @@ -137,6 +147,7 @@ runs: PR_JSON="$(gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}" 2>/dev/null)" || skip "Could not fetch PR #$PR_NUMBER from the API." BASE_SHA="$(printf '%s' "$PR_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["base"]["sha"])' 2>/dev/null)" || skip "Could not parse base SHA from the PR API." HEAD_SHA="$(printf '%s' "$PR_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["head"]["sha"])' 2>/dev/null)" || skip "Could not parse head SHA from the PR API." + HEAD_REF="$(printf '%s' "$PR_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["head"]["ref"])' 2>/dev/null)" || HEAD_REF="" BASE_REF="$(printf '%s' "$PR_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["base"]["ref"])' 2>/dev/null)" || BASE_REF="" BASE_REPO="$(printf '%s' "$PR_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["base"]["repo"]["full_name"])' 2>/dev/null)" || skip "Could not parse base repo from the PR API." HEAD_REPO="$(printf '%s' "$PR_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["head"]["repo"]["full_name"])' 2>/dev/null)" || skip "Could not parse head repo from the PR API." @@ -151,9 +162,12 @@ runs: echo "pr_number=$PR_NUMBER" echo "base_sha=$BASE_SHA" echo "head_sha=$HEAD_SHA" + echo "head_ref=$HEAD_REF" echo "base_ref=$BASE_REF" echo "base_repo=$BASE_REPO" echo "head_repo=$HEAD_REPO" + # same_repo gates pushing the head analysis: forks give a read-only token. + if [ "$HEAD_REPO" = "$REPOSITORY" ]; then echo "same_repo=true"; else echo "same_repo=false"; fi } >> "$GITHUB_OUTPUT" echo "Resolved PR #$PR_NUMBER (base=$BASE_REPO@$BASE_SHA head=$HEAD_REPO@$HEAD_SHA) via $EVENT" @@ -576,6 +590,62 @@ runs: "${RUNNER_TEMP}/cb-agent-model" \ "${RUNNER_TEMP}/cb-parsing-model" + # Commit the generated head analysis (+ health report) to the PR branch so the + # hosted webview can fetch .codeboarding/analysis.json at the head SHA and open + # this PR's head-vs-base diff. Same-repo PRs only — the token is read-only on + # forks (the step is skipped there and the webview link is omitted). The push + # creates a NEW head commit; its SHA (webview_sha) is what the comment links to. + - name: Commit head analysis to PR branch + id: commit_head + if: >- + steps.guard.outputs.skip != 'true' + && inputs.commit_head_analysis == 'true' + && steps.guard.outputs.same_repo == 'true' + && inputs.webview_base_url != '' + shell: bash + working-directory: target-repo + env: + GH_TOKEN: ${{ inputs.github_token }} + HEAD_DIR: ${{ steps.base.outputs.head_dir }} + HEAD_REF: ${{ steps.guard.outputs.head_ref }} + HEAD_SHA: ${{ steps.guard.outputs.head_sha }} + REPOSITORY: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + run: | + echo "ready=false" >> "$GITHUB_OUTPUT" + echo "webview_sha=$HEAD_SHA" >> "$GITHUB_OUTPUT" + [ -n "$HEAD_REF" ] || { echo "::notice::No head branch ref resolved; skipping head-analysis commit."; exit 0; } + [ -f "$HEAD_DIR/analysis.json" ] || { echo "::notice::No head analysis.json to commit."; exit 0; } + + mkdir -p .codeboarding/health + cp "$HEAD_DIR/analysis.json" .codeboarding/analysis.json + if [ -f "$HEAD_DIR/health/health_report.json" ]; then + cp "$HEAD_DIR/health/health_report.json" .codeboarding/health/health_report.json + fi + + git add .codeboarding/analysis.json .codeboarding/health/health_report.json 2>/dev/null || git add .codeboarding/analysis.json + if git diff --cached --quiet; then + echo "::notice::Head analysis unchanged; nothing to commit." + echo "ready=true" >> "$GITHUB_OUTPUT" # already committed at this SHA → webview can read it + exit 0 + fi + + git config user.name "codeboarding[bot]" + git config user.email "codeboarding[bot]@users.noreply.github.com" + git commit -m "chore(codeboarding): update architecture analysis [skip ci]" >/dev/null + + # Push to the PR head branch. The checkout used persist-credentials:false, so + # authenticate the push explicitly with the workflow token (same-repo only). + AUTH_URL="https://x-access-token:${GH_TOKEN}@${SERVER_URL#https://}/${REPOSITORY}.git" + if git push "$AUTH_URL" "HEAD:refs/heads/${HEAD_REF}" 2>/dev/null; then + NEW_SHA="$(git rev-parse HEAD)" + echo "webview_sha=$NEW_SHA" >> "$GITHUB_OUTPUT" + echo "ready=true" >> "$GITHUB_OUTPUT" + echo "Committed head analysis to ${HEAD_REF} as ${NEW_SHA}." + else + echo "::warning::Could not push head analysis to ${HEAD_REF}; the webview link will be omitted." + fi + - name: Diff analyses → Mermaid if: steps.guard.outputs.skip != 'true' id: diagram @@ -634,6 +704,12 @@ runs: TRUNC: ${{ steps.diagram.outputs.truncated }} PR: ${{ steps.guard.outputs.pr_number }} ISSUES: ${{ steps.health.outputs.issues }} + WEBVIEW_BASE: ${{ inputs.webview_base_url }} + # SHA the head analysis.json was committed at (post-push), and whether that + # commit succeeded — gates the webview "explore in browser" link. + WEBVIEW_SHA: ${{ steps.commit_head.outputs.webview_sha }} + WEBVIEW_READY: ${{ steps.commit_head.outputs.ready }} + BASE_SHA: ${{ steps.guard.outputs.base_sha }} run: | BODY_FILE=$(mktemp) OWNER="${OWNER_REPO%%/*}"; REPO="${OWNER_REPO##*/}" @@ -646,11 +722,17 @@ runs: } # CTA footer: an editor link (proxy deep-link when CTA_BASE is set, else the - # extension's https listing — GitHub strips vscode:/cursor:) plus the ⚠️ banner. + # extension's https listing — GitHub strips vscode:/cursor:), the ⚠️ banner, + # and — when the head analysis was committed (WEBVIEW_READY) — a webview + # "explore in browser" link to this PR's head-vs-base diff. cta() { + local extra=() + if [ "$WEBVIEW_READY" = "true" ]; then + extra+=(--webview-ready --webview-base "$WEBVIEW_BASE" --head-sha "$WEBVIEW_SHA" --base-sha "$BASE_SHA") + fi python3 "$ACTION_PATH/scripts/build_cta.py" \ --cta-base "$CTA_BASE" --owner "$OWNER" --repo "$REPO" --pr "$PR" \ - --repo-path "$TARGET_REPO" --issues "${ISSUES:-0}" + --repo-path "$TARGET_REPO" --issues "${ISSUES:-0}" "${extra[@]}" } { diff --git a/scripts/build_cta.py b/scripts/build_cta.py index 5ee509d..8cb29aa 100644 --- a/scripts/build_cta.py +++ b/scripts/build_cta.py @@ -8,8 +8,9 @@ custom ``vscode:``/``cursor:`` schemes — a deep link would render as dead text — so the editor link points at the extension's plain-https listing instead (VS Code Marketplace, Cursor via Open VSX), which is the only clickable option. A no-install -hosted-webview ("explore in browser") tier is intentionally deferred (see -docs/COMMIT_STRATEGY.md) — the committed analysis already supports it later. +hosted-webview ("explore in browser") line is added when ``webview_ready`` — i.e. the +head ``analysis.json`` was committed to the PR branch and this isn't a fork PR — so +the webview can fetch a committed analysis at the head SHA (see docs/COMMIT_STRATEGY.md). Editor coverage is deliberately limited to **VS Code and Cursor**. Per the 2025 Stack Overflow Developer Survey (https://survey.stackoverflow.co/2025/technology/), @@ -58,7 +59,37 @@ def detect_editors(repo_path: Path) -> list[str]: } -def build_cta(cta_base: str, owner: str, repo: str, pr: str, repo_path: Path, issues: int = 0) -> str: +def build_webview_link(webview_base: str, owner: str, repo: str, head_sha: str, base_sha: str) -> str | None: + """Return the markdown "explore in browser" line, or None if not buildable. + + Deep-links the hosted webview straight to this PR's head-vs-base architecture + diff: ``?repo=owner/repo&ref=&compare=``. Pinned to exact + SHAs so the committed ``analysis.json`` the webview fetches matches this run. For + a private repo the webview itself sends the viewer through GitHub sign-in and then + loads the same diff. Returns None when the base/head pieces aren't all present. + """ + if not (webview_base and owner and repo and head_sha): + return None + base = webview_base.rstrip("/") + params = {"repo": f"{owner}/{repo}", "ref": head_sha} + if base_sha: + params["compare"] = base_sha + return f"🌐 [**Explore this PR’s architecture in your browser →**]({base}/?{urlencode(params)})" + + +def build_cta( + cta_base: str, + owner: str, + repo: str, + pr: str, + repo_path: Path, + issues: int = 0, + *, + webview_base: str = "", + head_sha: str = "", + base_sha: str = "", + webview_ready: bool = False, +) -> str: """Return the markdown CTA footer: a health-warning banner plus an editor link. With a ``cta_base`` proxy the links route through it (owner/repo/pr tracked), @@ -66,12 +97,22 @@ def build_cta(cta_base: str, owner: str, repo: str, pr: str, repo_path: Path, is a proxy the editor link is the extension's https listing (GitHub strips custom ``vscode:``/``cursor:`` schemes), and the redundant install link is dropped. The ⚠️ banner shows whenever ``issues > 0``. + + When ``webview_ready`` (the head ``analysis.json`` was committed and this isn't a + fork PR) a "explore in browser" line deep-links the hosted webview to this PR's + head-vs-base diff. Otherwise that line is omitted (the webview couldn't fetch a + committed analysis at the head SHA). """ parts: list[str] = [] if issues > 0: noun = "issue" if issues == 1 else "issues" parts.append(f"⚠️ **{issues} architecture {noun} found** — open CodeBoarding to explore them.") + if webview_ready: + webview_line = build_webview_link(webview_base, owner, repo, head_sha, base_sha) + if webview_line: + parts.append(webview_line) + editors = detect_editors(repo_path) if cta_base: base = cta_base.rstrip("/") @@ -104,13 +145,25 @@ def main() -> int: p.add_argument("--pr", required=True) p.add_argument("--repo-path", required=True, type=Path, help="Path to the analyzed repo checkout") p.add_argument("--issues", default="0", help="Real architecture-issue count (0 -> no warning banner)") + p.add_argument("--webview-base", default="", help="Hosted webview base URL (e.g. https://app.codeboarding.org)") + p.add_argument("--head-sha", default="", help="PR head SHA the webview link pins to") + p.add_argument("--base-sha", default="", help="PR base SHA the webview link compares against") + p.add_argument( + "--webview-ready", + action="store_true", + help="Emit the webview link (head analysis.json was committed; not a fork PR)", + ) args = p.parse_args() try: issues = int(args.issues or 0) except ValueError: issues = 0 - print(build_cta(args.cta_base, args.owner, args.repo, args.pr, args.repo_path, issues)) + print(build_cta( + args.cta_base, args.owner, args.repo, args.pr, args.repo_path, issues, + webview_base=args.webview_base, head_sha=args.head_sha, base_sha=args.base_sha, + webview_ready=args.webview_ready, + )) return 0 diff --git a/tests/test_build_cta.py b/tests/test_build_cta.py index 1373315..d6264b8 100644 --- a/tests/test_build_cta.py +++ b/tests/test_build_cta.py @@ -77,5 +77,51 @@ def test_trailing_slash_in_base_is_normalized(self): self.assertEqual(a, b) +class TestWebviewLink(unittest.TestCase): + WV = "https://app.codeboarding.org" + + def test_link_built_with_head_ref_and_compare_base(self): + link = bc.build_webview_link(self.WV, "Org", "Repo", "headsha", "basesha") + self.assertIn("https://app.codeboarding.org/?", link) + self.assertIn("repo=Org%2FRepo", link) + self.assertIn("ref=headsha", link) + self.assertIn("compare=basesha", link) + + def test_link_omits_compare_when_no_base(self): + link = bc.build_webview_link(self.WV, "o", "r", "headsha", "") + self.assertIn("ref=headsha", link) + self.assertNotIn("compare=", link) + + def test_link_none_without_head_sha_or_base(self): + self.assertIsNone(bc.build_webview_link(self.WV, "o", "r", "", "basesha")) + self.assertIsNone(bc.build_webview_link("", "o", "r", "headsha", "basesha")) + + def test_cta_emits_webview_line_when_ready(self): + out = bc.build_cta( + "", "Org", "Repo", "9", repo_with(), issues=0, + webview_base=self.WV, head_sha="headsha", base_sha="basesha", webview_ready=True, + ) + self.assertIn("Explore this PR", out) + self.assertIn("ref=headsha", out) + self.assertIn("compare=basesha", out) + + def test_cta_omits_webview_line_when_not_ready(self): + # Fork PR / head analysis not committed -> webview can't fetch at head SHA. + out = bc.build_cta( + "", "Org", "Repo", "9", repo_with(), issues=0, + webview_base=self.WV, head_sha="headsha", base_sha="basesha", webview_ready=False, + ) + self.assertNotIn("Explore this PR", out) + # Editor CTA is still present regardless. + self.assertIn("Open in VS Code", out) + + def test_cta_omits_webview_line_when_ready_but_no_base_url(self): + out = bc.build_cta( + "", "Org", "Repo", "9", repo_with(), issues=0, + webview_base="", head_sha="headsha", base_sha="basesha", webview_ready=True, + ) + self.assertNotIn("Explore this PR", out) + + if __name__ == "__main__": unittest.main() From 7e07060289fb7516657e6c428fdec5e631fbfd72 Mon Sep 17 00:00:00 2001 From: Svilen Stefanov Date: Thu, 11 Jun 2026 17:40:04 +0200 Subject: [PATCH 2/4] Allow writes in CI for analysis.json to be commited --- .github/workflows/codeboarding.yml | 4 +++- README.md | 5 ++++- action.yml | 15 ++++++++++--- scripts/build_cta.py | 19 +++++++++++----- tests/test_build_cta.py | 36 +++++++++++++++++++++++++----- 5 files changed, 63 insertions(+), 16 deletions(-) diff --git a/.github/workflows/codeboarding.yml b/.github/workflows/codeboarding.yml index 451103d..e5362fe 100644 --- a/.github/workflows/codeboarding.yml +++ b/.github/workflows/codeboarding.yml @@ -11,7 +11,9 @@ on: types: [created] permissions: - contents: read + # write: the action commits the generated .codeboarding/analysis.json back to the + # PR branch so the webview can open this PR's diff at the head SHA (same-repo PRs). + contents: write pull-requests: write issues: write diff --git a/README.md b/README.md index 0927c61..7744753 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,9 @@ on: types: [created] permissions: - contents: read + # write lets the action commit analysis.json to the PR branch so the comment can + # link to the webview diff. Drop to `read` to keep the comment without that link. + contents: write pull-requests: write issues: write @@ -156,6 +158,7 @@ The command needs the `issue_comment` trigger and runs from your default branch | `llm_api_key` | required | Your LLM provider API key (see `llm_provider`). | | `llm_provider` | `openrouter` | Provider for the key, mapped to `_API_KEY` (e.g. `anthropic`, `openai`, `google`). | | `github_token` | `${{ github.token }}` | Token used to post or update the PR comment. | +| `push_token` | `${{ github.token }}` | Token used to push the generated `analysis.json` to the PR branch (for the webview link). The workflow token can push when the workflow grants `permissions: contents: write`. Separate from `github_token` so commenting can use a GitHub App token while the push uses the workflow token. | | `engine_ref` | `v0.12.0` | CodeBoarding engine ref. Pin for reproducibility. | | `depth_level` | `1` | Analysis depth, 1 to 3. Higher is slower and richer. | | `render_depth` | `1` | Display depth for the PR diagram. Keep `1` for a clean top-level view. | diff --git a/action.yml b/action.yml index be6427a..e9b24b8 100644 --- a/action.yml +++ b/action.yml @@ -18,6 +18,10 @@ inputs: description: 'GITHUB_TOKEN used to post the PR comment. Defaults to the workflow token.' required: false default: ${{ github.token }} + push_token: + description: 'Token used to push the generated .codeboarding/analysis.json to the PR branch (for the webview link). Defaults to the workflow github.token, which can push when the calling workflow grants "permissions: contents: write". Kept separate from github_token so commenting can use a GitHub App token while the push uses the workflow token (whose write access the consumer controls).' + required: false + default: ${{ github.token }} engine_ref: description: 'Git ref (tag/branch/SHA) of CodeBoarding/CodeBoarding used as the analysis engine. Pinned to a release for reproducibility; override to track a newer ref.' required: false @@ -605,7 +609,10 @@ runs: shell: bash working-directory: target-repo env: - GH_TOKEN: ${{ inputs.github_token }} + # Push with push_token (defaults to the workflow github.token, gated by the + # consumer's `permissions: contents: write`) — NOT github_token, which may be + # a GitHub App token used only for commenting and need not have write access. + GH_TOKEN: ${{ inputs.push_token }} HEAD_DIR: ${{ steps.base.outputs.head_dir }} HEAD_REF: ${{ steps.guard.outputs.head_ref }} HEAD_SHA: ${{ steps.guard.outputs.head_sha }} @@ -636,14 +643,16 @@ runs: # Push to the PR head branch. The checkout used persist-credentials:false, so # authenticate the push explicitly with the workflow token (same-repo only). + # Requires `contents: write` on the job's token — a read-only token (the + # default) is rejected here; the push error below names the cause. AUTH_URL="https://x-access-token:${GH_TOKEN}@${SERVER_URL#https://}/${REPOSITORY}.git" - if git push "$AUTH_URL" "HEAD:refs/heads/${HEAD_REF}" 2>/dev/null; then + if git push "$AUTH_URL" "HEAD:refs/heads/${HEAD_REF}"; then NEW_SHA="$(git rev-parse HEAD)" echo "webview_sha=$NEW_SHA" >> "$GITHUB_OUTPUT" echo "ready=true" >> "$GITHUB_OUTPUT" echo "Committed head analysis to ${HEAD_REF} as ${NEW_SHA}." else - echo "::warning::Could not push head analysis to ${HEAD_REF}; the webview link will be omitted." + echo "::warning::Could not push head analysis to ${HEAD_REF}; the webview link will be omitted. Most likely the job's token lacks 'contents: write' (add 'permissions: contents: write' to the calling workflow), or the branch is protected against this pusher." fi - name: Diff analyses → Mermaid diff --git a/scripts/build_cta.py b/scripts/build_cta.py index 8cb29aa..0a4947e 100644 --- a/scripts/build_cta.py +++ b/scripts/build_cta.py @@ -159,11 +159,20 @@ def main() -> int: issues = int(args.issues or 0) except ValueError: issues = 0 - print(build_cta( - args.cta_base, args.owner, args.repo, args.pr, args.repo_path, issues, - webview_base=args.webview_base, head_sha=args.head_sha, base_sha=args.base_sha, - webview_ready=args.webview_ready, - )) + print( + build_cta( + args.cta_base, + args.owner, + args.repo, + args.pr, + args.repo_path, + issues, + webview_base=args.webview_base, + head_sha=args.head_sha, + base_sha=args.base_sha, + webview_ready=args.webview_ready, + ) + ) return 0 diff --git a/tests/test_build_cta.py b/tests/test_build_cta.py index d6264b8..22226fc 100644 --- a/tests/test_build_cta.py +++ b/tests/test_build_cta.py @@ -98,8 +98,16 @@ def test_link_none_without_head_sha_or_base(self): def test_cta_emits_webview_line_when_ready(self): out = bc.build_cta( - "", "Org", "Repo", "9", repo_with(), issues=0, - webview_base=self.WV, head_sha="headsha", base_sha="basesha", webview_ready=True, + "", + "Org", + "Repo", + "9", + repo_with(), + issues=0, + webview_base=self.WV, + head_sha="headsha", + base_sha="basesha", + webview_ready=True, ) self.assertIn("Explore this PR", out) self.assertIn("ref=headsha", out) @@ -108,8 +116,16 @@ def test_cta_emits_webview_line_when_ready(self): def test_cta_omits_webview_line_when_not_ready(self): # Fork PR / head analysis not committed -> webview can't fetch at head SHA. out = bc.build_cta( - "", "Org", "Repo", "9", repo_with(), issues=0, - webview_base=self.WV, head_sha="headsha", base_sha="basesha", webview_ready=False, + "", + "Org", + "Repo", + "9", + repo_with(), + issues=0, + webview_base=self.WV, + head_sha="headsha", + base_sha="basesha", + webview_ready=False, ) self.assertNotIn("Explore this PR", out) # Editor CTA is still present regardless. @@ -117,8 +133,16 @@ def test_cta_omits_webview_line_when_not_ready(self): def test_cta_omits_webview_line_when_ready_but_no_base_url(self): out = bc.build_cta( - "", "Org", "Repo", "9", repo_with(), issues=0, - webview_base="", head_sha="headsha", base_sha="basesha", webview_ready=True, + "", + "Org", + "Repo", + "9", + repo_with(), + issues=0, + webview_base="", + head_sha="headsha", + base_sha="basesha", + webview_ready=True, ) self.assertNotIn("Explore this PR", out) From d1a2dcc67ffb7626fbe2317a584bab694bbd8092 Mon Sep 17 00:00:00 2001 From: "codeboarding[bot]" Date: Thu, 11 Jun 2026 15:51:00 +0000 Subject: [PATCH 3/4] chore(codeboarding): update architecture analysis [skip ci] --- .codeboarding/analysis.json | 574 +++++++++++++++++++++--- .codeboarding/health/health_report.json | 80 ++++ 2 files changed, 603 insertions(+), 51 deletions(-) create mode 100644 .codeboarding/health/health_report.json diff --git a/.codeboarding/analysis.json b/.codeboarding/analysis.json index 714a920..f4ebb7a 100644 --- a/.codeboarding/analysis.json +++ b/.codeboarding/analysis.json @@ -1,94 +1,566 @@ { "metadata": { - "generated_at": "2026-05-25T20:11:22.655182+00:00", - "commit_hash": "189e8f32abca4ac64d2449da313fc3e553e66b2e", + "generated_at": "2026-06-11T15:51:00.405452+00:00", + "commit_hash": "7e07060289fb7516657e6c428fdec5e631fbfd72", "repo_name": "CodeBoarding-action", "depth_level": 1, "file_coverage_summary": { - "total_files": 3, - "analyzed": 0, - "not_analyzed": 3, + "total_files": 15, + "analyzed": 3, + "not_analyzed": 12, "not_analyzed_by_reason": { - "other": 3 + "other": 9, + "codeboardingignore": 3 } } }, - "description": "This architecture represents the lifecycle of a remote documentation analysis job within a GitHub Action environment. The flow begins with environment normalization, proceeds through an asynchronous polling-based job orchestration with a remote API, and concludes with the extraction and mapping of results to the GitHub runner's output context.", - "files": {}, - "methods_index": {}, + "description": "The CodeBoarding-action pipeline automates structural code analysis and visualization by orchestrating engine execution across Git branches, generating visual diffs of code changes, and integrating these insights into GitHub PRs via IDE-specific deep links.", + "files": { + "scripts/cb_engine.py": { + "method_keys": [ + "scripts/cb_engine.py|scripts.cb_engine._log_path", + "scripts/cb_engine.py|scripts.cb_engine._clear_dir", + "scripts/cb_engine.py|scripts.cb_engine.validate_base_analysis", + "scripts/cb_engine.py|scripts.cb_engine.run_base", + "scripts/cb_engine.py|scripts.cb_engine.run_seed", + "scripts/cb_engine.py|scripts.cb_engine.run_head", + "scripts/cb_engine.py|scripts.cb_engine._count_report_issues", + "scripts/cb_engine.py|scripts.cb_engine._count_health_report", + "scripts/cb_engine.py|scripts.cb_engine.run_health", + "scripts/cb_engine.py|scripts.cb_engine.main" + ] + }, + "scripts/diff_to_mermaid.py": { + "method_keys": [ + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid.load_analysis", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._comp_id", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._comp_name", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._file_methods", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._methods_by_file", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._has_structural_changes", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._has_method_changes", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._rel_key", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._diff_relations", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._has_changes", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._diff_components", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid.build_diff", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._sanitize", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._esc", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._truncate", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._display_status", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._Scope", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._Scope.__init__", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._Scope.resolve", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._filter_changed", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._filter_changed.touches", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._init_directive", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._count_changed_components", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._has_changed_relations", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid.render_mermaid", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid.render_mermaid.build", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid.render_mermaid.build.emit_edges", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid.render_mermaid.build.emit_level", + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid.main" + ] + }, + "scripts/build_cta.py": { + "method_keys": [ + "scripts/build_cta.py|scripts.build_cta.detect_editors", + "scripts/build_cta.py|scripts.build_cta.build_cta", + "scripts/build_cta.py|scripts.build_cta.build_cta.link", + "scripts/build_cta.py|scripts.build_cta.main" + ] + } + }, + "methods_index": { + "scripts/cb_engine.py|scripts.cb_engine._log_path": { + "file_path": "scripts/cb_engine.py", + "qualified_name": "scripts.cb_engine._log_path", + "start_line": 32, + "end_line": 33, + "type": "FUNCTION" + }, + "scripts/cb_engine.py|scripts.cb_engine._clear_dir": { + "file_path": "scripts/cb_engine.py", + "qualified_name": "scripts.cb_engine._clear_dir", + "start_line": 36, + "end_line": 42, + "type": "FUNCTION" + }, + "scripts/cb_engine.py|scripts.cb_engine.validate_base_analysis": { + "file_path": "scripts/cb_engine.py", + "qualified_name": "scripts.cb_engine.validate_base_analysis", + "start_line": 45, + "end_line": 76, + "type": "FUNCTION" + }, + "scripts/cb_engine.py|scripts.cb_engine.run_base": { + "file_path": "scripts/cb_engine.py", + "qualified_name": "scripts.cb_engine.run_base", + "start_line": 79, + "end_line": 91, + "type": "FUNCTION" + }, + "scripts/cb_engine.py|scripts.cb_engine.run_seed": { + "file_path": "scripts/cb_engine.py", + "qualified_name": "scripts.cb_engine.run_seed", + "start_line": 94, + "end_line": 121, + "type": "FUNCTION" + }, + "scripts/cb_engine.py|scripts.cb_engine.run_head": { + "file_path": "scripts/cb_engine.py", + "qualified_name": "scripts.cb_engine.run_head", + "start_line": 124, + "end_line": 153, + "type": "FUNCTION" + }, + "scripts/cb_engine.py|scripts.cb_engine._count_report_issues": { + "file_path": "scripts/cb_engine.py", + "qualified_name": "scripts.cb_engine._count_report_issues", + "start_line": 156, + "end_line": 169, + "type": "FUNCTION" + }, + "scripts/cb_engine.py|scripts.cb_engine._count_health_report": { + "file_path": "scripts/cb_engine.py", + "qualified_name": "scripts.cb_engine._count_health_report", + "start_line": 172, + "end_line": 180, + "type": "FUNCTION" + }, + "scripts/cb_engine.py|scripts.cb_engine.run_health": { + "file_path": "scripts/cb_engine.py", + "qualified_name": "scripts.cb_engine.run_health", + "start_line": 183, + "end_line": 212, + "type": "FUNCTION" + }, + "scripts/cb_engine.py|scripts.cb_engine.main": { + "file_path": "scripts/cb_engine.py", + "qualified_name": "scripts.cb_engine.main", + "start_line": 215, + "end_line": 257, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid.load_analysis": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid.load_analysis", + "start_line": 50, + "end_line": 54, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._comp_id": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid._comp_id", + "start_line": 60, + "end_line": 61, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._comp_name": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid._comp_name", + "start_line": 64, + "end_line": 65, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._file_methods": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid._file_methods", + "start_line": 68, + "end_line": 69, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._methods_by_file": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid._methods_by_file", + "start_line": 72, + "end_line": 79, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._has_structural_changes": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid._has_structural_changes", + "start_line": 82, + "end_line": 85, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._has_method_changes": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid._has_method_changes", + "start_line": 88, + "end_line": 93, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._rel_key": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid._rel_key", + "start_line": 96, + "end_line": 99, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._diff_relations": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid._diff_relations", + "start_line": 102, + "end_line": 148, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._has_changes": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid._has_changes", + "start_line": 151, + "end_line": 159, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._diff_components": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid._diff_components", + "start_line": 162, + "end_line": 207, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid.build_diff": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid.build_diff", + "start_line": 210, + "end_line": 217, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._sanitize": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid._sanitize", + "start_line": 223, + "end_line": 225, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._esc": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid._esc", + "start_line": 247, + "end_line": 253, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._truncate": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid._truncate", + "start_line": 256, + "end_line": 258, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._display_status": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid._display_status", + "start_line": 261, + "end_line": 262, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._Scope": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid._Scope", + "start_line": 265, + "end_line": 309, + "type": "CLASS" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._Scope.__init__": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid._Scope.__init__", + "start_line": 276, + "end_line": 298, + "type": "METHOD" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._Scope.resolve": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid._Scope.resolve", + "start_line": 300, + "end_line": 309, + "type": "METHOD" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._filter_changed": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid._filter_changed", + "start_line": 312, + "end_line": 354, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._filter_changed.touches": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid._filter_changed.touches", + "start_line": 345, + "end_line": 347, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._init_directive": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid._init_directive", + "start_line": 357, + "end_line": 376, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._count_changed_components": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid._count_changed_components", + "start_line": 379, + "end_line": 386, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid._has_changed_relations": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid._has_changed_relations", + "start_line": 389, + "end_line": 393, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid.render_mermaid": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid.render_mermaid", + "start_line": 396, + "end_line": 521, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid.render_mermaid.build": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid.render_mermaid.build", + "start_line": 424, + "end_line": 497, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid.render_mermaid.build.emit_edges": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid.render_mermaid.build.emit_edges", + "start_line": 435, + "end_line": 447, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid.render_mermaid.build.emit_level": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid.render_mermaid.build.emit_level", + "start_line": 449, + "end_line": 466, + "type": "FUNCTION" + }, + "scripts/diff_to_mermaid.py|scripts.diff_to_mermaid.main": { + "file_path": "scripts/diff_to_mermaid.py", + "qualified_name": "scripts.diff_to_mermaid.main", + "start_line": 527, + "end_line": 568, + "type": "FUNCTION" + }, + "scripts/build_cta.py|scripts.build_cta.detect_editors": { + "file_path": "scripts/build_cta.py", + "qualified_name": "scripts.build_cta.detect_editors", + "start_line": 35, + "end_line": 47, + "type": "FUNCTION" + }, + "scripts/build_cta.py|scripts.build_cta.build_cta": { + "file_path": "scripts/build_cta.py", + "qualified_name": "scripts.build_cta.build_cta", + "start_line": 61, + "end_line": 96, + "type": "FUNCTION" + }, + "scripts/build_cta.py|scripts.build_cta.build_cta.link": { + "file_path": "scripts/build_cta.py", + "qualified_name": "scripts.build_cta.build_cta.link", + "start_line": 79, + "end_line": 80, + "type": "FUNCTION" + }, + "scripts/build_cta.py|scripts.build_cta.main": { + "file_path": "scripts/build_cta.py", + "qualified_name": "scripts.build_cta.main", + "start_line": 99, + "end_line": 114, + "type": "FUNCTION" + } + }, "components": [ { - "name": "Environment & Context Resolver", - "description": "Normalizes GitHub environment variables and user inputs into a canonical format. Handles conversion of SSH repository URLs to HTTPS for remote backend compatibility and validates the presence of required secrets like CODEBOARDING_API_KEY.", - "key_entities": [], + "name": "Analysis Orchestrator", + "description": "Coordinates the execution flow of the CodeBoarding engine, managing the lifecycle of base, head, and health analysis runs. The unclustered connections reveal a complex internal state machine (scripts.cb_engine.run_base, scripts.cb_engine.run_head, scripts.cb_engine.run_health, scripts.cb_engine.validate_base_analysis) that defines the core pipeline logic of the GitHub Action.", + "key_entities": [ + { + "qualified_name": "scripts.cb_engine.main", + "reference_file": "scripts/cb_engine.py", + "reference_start_line": 215, + "reference_end_line": 257 + }, + { + "qualified_name": "scripts.cb_engine.run_base", + "reference_file": "scripts/cb_engine.py", + "reference_start_line": 79, + "reference_end_line": 91 + }, + { + "qualified_name": "scripts.cb_engine.run_head", + "reference_file": "scripts/cb_engine.py", + "reference_start_line": 124, + "reference_end_line": 153 + }, + { + "qualified_name": "scripts.cb_engine.validate_base_analysis", + "reference_file": "scripts/cb_engine.py", + "reference_start_line": 45, + "reference_end_line": 76 + } + ], "source_cluster_ids": [ 1 ], - "file_methods": [], + "file_methods": [ + { + "file_path": "scripts/cb_engine.py", + "methods": [ + "scripts.cb_engine._log_path", + "scripts.cb_engine._clear_dir", + "scripts.cb_engine.validate_base_analysis", + "scripts.cb_engine.run_base", + "scripts.cb_engine.run_seed", + "scripts.cb_engine.run_head", + "scripts.cb_engine._count_report_issues", + "scripts.cb_engine._count_health_report", + "scripts.cb_engine.run_health", + "scripts.cb_engine.main" + ] + } + ], "component_id": "1", - "can_expand": false + "can_expand": true }, { - "name": "Remote Job Orchestrator", - "description": "Core engine managing the lifecycle of remote analysis jobs, including initial registration and a persistent polling loop. Encapsulates HTTP communication logic, exponential backoff via 60-second intervals, and timeout enforcement with a 90-minute limit.", - "key_entities": [], + "name": "Visual Diff Generator", + "description": "Transforms raw analysis data and structural changes into visual representations, specifically Mermaid.js diagrams, highlighting architectural shifts. The new connections (scripts.diff_to_mermaid._diff_components, scripts.diff_to_mermaid._diff_relations, scripts.diff_to_mermaid._has_structural_changes) indicate a sophisticated logic for identifying architectural regressions or evolutions.", + "key_entities": [ + { + "qualified_name": "scripts.diff_to_mermaid.main", + "reference_file": "scripts/diff_to_mermaid.py", + "reference_start_line": 527, + "reference_end_line": 568 + }, + { + "qualified_name": "scripts.diff_to_mermaid.build_diff", + "reference_file": "scripts/diff_to_mermaid.py", + "reference_start_line": 210, + "reference_end_line": 217 + }, + { + "qualified_name": "scripts.diff_to_mermaid.render_mermaid", + "reference_file": "scripts/diff_to_mermaid.py", + "reference_start_line": 396, + "reference_end_line": 521 + }, + { + "qualified_name": "scripts.diff_to_mermaid._diff_components", + "reference_file": "scripts/diff_to_mermaid.py", + "reference_start_line": 162, + "reference_end_line": 207 + } + ], "source_cluster_ids": [ 2, - 3 + 3, + 4, + 5 + ], + "file_methods": [ + { + "file_path": "scripts/diff_to_mermaid.py", + "methods": [ + "scripts.diff_to_mermaid.load_analysis", + "scripts.diff_to_mermaid._comp_id", + "scripts.diff_to_mermaid._comp_name", + "scripts.diff_to_mermaid._file_methods", + "scripts.diff_to_mermaid._methods_by_file", + "scripts.diff_to_mermaid._has_structural_changes", + "scripts.diff_to_mermaid._has_method_changes", + "scripts.diff_to_mermaid._rel_key", + "scripts.diff_to_mermaid._diff_relations", + "scripts.diff_to_mermaid._has_changes", + "scripts.diff_to_mermaid._diff_components", + "scripts.diff_to_mermaid.build_diff", + "scripts.diff_to_mermaid._sanitize", + "scripts.diff_to_mermaid._esc", + "scripts.diff_to_mermaid._truncate", + "scripts.diff_to_mermaid._display_status", + "scripts.diff_to_mermaid._Scope", + "scripts.diff_to_mermaid._Scope.__init__", + "scripts.diff_to_mermaid._Scope.resolve", + "scripts.diff_to_mermaid._filter_changed", + "scripts.diff_to_mermaid._filter_changed.touches", + "scripts.diff_to_mermaid._init_directive", + "scripts.diff_to_mermaid._count_changed_components", + "scripts.diff_to_mermaid._has_changed_relations", + "scripts.diff_to_mermaid.render_mermaid", + "scripts.diff_to_mermaid.render_mermaid.build", + "scripts.diff_to_mermaid.render_mermaid.build.emit_edges", + "scripts.diff_to_mermaid.render_mermaid.build.emit_level", + "scripts.diff_to_mermaid.main" + ] + } ], - "file_methods": [], "component_id": "2", - "can_expand": false + "can_expand": true }, { - "name": "Artifact & Result Processor", - "description": "Parses the final JSON payload from the remote server once job status reaches COMPLETED. Extracts generated documentation content and maps it to GitHub Action output parameters for availability in subsequent workflow steps.", - "key_entities": [], + "name": "UX & Integration Layer", + "description": "Manages the interaction between the action and the GitHub environment, including the generation of PR comments, status updates, and user-facing links. The addition of scripts.build_cta.py introduces specific logic for generating 'Call to Action' links and webview integrations, expanding the layer's responsibility from simple reporting to interactive developer experience (DevEx) tooling.", + "key_entities": [ + { + "qualified_name": "scripts.build_cta.main", + "reference_file": "scripts/build_cta.py", + "reference_start_line": 99, + "reference_end_line": 114 + }, + { + "qualified_name": "scripts.build_cta.build_cta", + "reference_file": "scripts/build_cta.py", + "reference_start_line": 61, + "reference_end_line": 96 + }, + { + "qualified_name": "scripts.build_cta.detect_editors", + "reference_file": "scripts/build_cta.py", + "reference_start_line": 35, + "reference_end_line": 47 + } + ], "source_cluster_ids": [ - 4, - 5 + 6 + ], + "file_methods": [ + { + "file_path": "scripts/build_cta.py", + "methods": [ + "scripts.build_cta.detect_editors", + "scripts.build_cta.build_cta", + "scripts.build_cta.build_cta.link", + "scripts.build_cta.main" + ] + } ], - "file_methods": [], "component_id": "3", - "can_expand": false + "can_expand": true } ], "components_relations": [ { - "relation": "passes resolved repository URL and branch to initiate remote job request", - "src_name": "Environment & Context Resolver", - "dst_name": "Remote Job Orchestrator", + "relation": "Provides analysis artifacts to", + "src_name": "Analysis Orchestrator", + "dst_name": "Visual Diff Generator", "src_id": "1", "dst_id": "2", "edge_count": 0, "is_static": false }, { - "relation": "hands off JSON response containing full job results after COMPLETED status detection", - "src_name": "Remote Job Orchestrator", - "dst_name": "Artifact & Result Processor", + "relation": "Passes rendered diagrams to", + "src_name": "Visual Diff Generator", + "dst_name": "UX & Integration Layer", "src_id": "2", "dst_id": "3", "edge_count": 0, "is_static": false - }, - { - "relation": "writes extracted documentation data to runner filesystem and sets action output variables", - "src_name": "Artifact & Result Processor", - "dst_name": "Artifact & Result Processor", - "src_id": "3", - "dst_id": "3", - "edge_count": 0, - "is_static": false - }, - { - "relation": "manages job configuration and polls for status updates via REST/HTTPS", - "src_name": "Remote Job Orchestrator", - "dst_name": "Remote Job Orchestrator", - "src_id": "2", - "dst_id": "2", - "edge_count": 0, - "is_static": false } ] } \ No newline at end of file diff --git a/.codeboarding/health/health_report.json b/.codeboarding/health/health_report.json new file mode 100644 index 0000000..96fec94 --- /dev/null +++ b/.codeboarding/health/health_report.json @@ -0,0 +1,80 @@ +{ + "repository_name": "CodeBoarding-action", + "timestamp": "2026-06-11T15:50:45.582495+00:00", + "overall_score": 0.9996183206106869, + "check_summaries": [ + { + "check_name": "function_size", + "description": "Checks that functions/methods do not exceed line count thresholds", + "check_type": "standard", + "total_entities_checked": 43, + "findings_count": 0, + "warning_count": 0, + "score": 1.0, + "finding_groups": [] + }, + { + "check_name": "fan_out", + "description": "Checks efferent coupling: how many other functions each function calls", + "check_type": "standard", + "total_entities_checked": 43, + "findings_count": 0, + "warning_count": 0, + "score": 1.0, + "finding_groups": [] + }, + { + "check_name": "fan_in", + "description": "Checks afferent coupling: how many other functions call each function", + "check_type": "standard", + "total_entities_checked": 43, + "findings_count": 0, + "warning_count": 0, + "score": 1.0, + "finding_groups": [] + }, + { + "check_name": "god_class", + "description": "Detects classes with too many methods, too much code, or too many outgoing dependencies", + "check_type": "standard", + "total_entities_checked": 1, + "findings_count": 0, + "warning_count": 0, + "score": 1.0, + "finding_groups": [] + }, + { + "check_name": "unused_code_diagnostics", + "description": "Detects unused imports, variables, functions, and dead code via LSP diagnostics", + "check_type": "standard", + "total_entities_checked": 1, + "findings_count": 1, + "warning_count": 0, + "score": 0.95, + "finding_groups": [ + { + "severity": "info", + "threshold": 0, + "description": "Code that cannot be reached during execution", + "entities": [ + { + "entity_name": "[unreachable_code] Type analysis indicates code is unreachable", + "file_path": "scripts/cb_engine.py", + "line_start": 159, + "line_end": 159, + "metric_value": 0.0 + } + ] + } + ] + } + ], + "file_summaries": [ + { + "file_path": "scripts/cb_engine.py", + "total_findings": 1, + "warning_findings": 0, + "composite_risk_score": 10.0 + } + ] +} \ No newline at end of file From b40e54bf67e2d2cc0ea63eb694c098f357ffcc12 Mon Sep 17 00:00:00 2001 From: "codeboarding[bot]" Date: Thu, 11 Jun 2026 16:41:17 +0000 Subject: [PATCH 4/4] chore(codeboarding): update architecture analysis [skip ci] --- .codeboarding/analysis.json | 45 +++++++++++++++---------- .codeboarding/health/health_report.json | 28 +++------------ 2 files changed, 32 insertions(+), 41 deletions(-) diff --git a/.codeboarding/analysis.json b/.codeboarding/analysis.json index 62330fe..a7d7e4f 100644 --- a/.codeboarding/analysis.json +++ b/.codeboarding/analysis.json @@ -1,7 +1,7 @@ { "metadata": { - "generated_at": "2026-06-11T16:36:04.958976+00:00", - "commit_hash": "e9aa6435ce9f0ffe6726d5c1a3758980f79f1c3b", + "generated_at": "2026-06-11T16:41:16.710439+00:00", + "commit_hash": "ba5d7787eaecfa38964dd4663606927d98e501ba", "repo_name": "CodeBoarding-action", "depth_level": 1, "file_coverage_summary": { @@ -14,7 +14,7 @@ } } }, - "description": "The CodeBoarding system orchestrates the analysis of git repositories, performs structural diffing between analysis states, and generates actionable visual feedback for pull requests by integrating with developer environments.", + "description": "The CodeBoarding-action architecture is a CI/CD pipeline that automates architectural observability by comparing codebase versions, generating visual Mermaid.js diffs, and providing actionable IDE deep-links within GitHub Pull Requests.", "files": { "scripts/cb_engine.py": { "method_keys": [ @@ -377,8 +377,8 @@ }, "components": [ { - "name": "Engine Orchestrator", - "description": "Manages the execution environment and lifecycle of the CodeBoarding analysis engine. It handles the checkout of different git references, executes the analysis, and validates the integrity of the generated data before downstream processing.", + "name": "Analysis Orchestrator", + "description": "Manages the execution lifecycle of the GitHub Action, including environment setup, triggering analysis, and data validation.", "key_entities": [ { "qualified_name": "scripts.cb_engine.main", @@ -429,8 +429,8 @@ "can_expand": true }, { - "name": "Visual Diff Engine", - "description": "The core analytical component that compares two sets of analysis data. It identifies structural modifications (additions, removals, or changes in relationships), filters for relevance to reduce diagram noise, and renders the final architectural diff using Mermaid.js syntax.", + "name": "Visual Diff Generator", + "description": "The core logic engine that compares structural metadata between codebase versions and renders differences into Mermaid.js graph syntax.", "key_entities": [ { "qualified_name": "scripts.diff_to_mermaid.main", @@ -451,10 +451,10 @@ "reference_end_line": 521 }, { - "qualified_name": "scripts.diff_to_mermaid._filter_changed", + "qualified_name": "scripts.diff_to_mermaid._diff_components", "reference_file": "scripts/diff_to_mermaid.py", - "reference_start_line": 312, - "reference_end_line": 354 + "reference_start_line": 162, + "reference_end_line": 207 } ], "source_cluster_ids": [ @@ -503,8 +503,8 @@ "can_expand": true }, { - "name": "Engagement & Feedback Manager", - "description": "Enhances the PR comment with interactive elements. It detects the developer's environment and generates deep-links to local IDEs or the CodeBoarding dashboard, transforming a static diagram into an actionable entry point for code review.", + "name": "UX & Integration Helper", + "description": "Manages the generation of user-facing outputs, including GitHub comments, status checks, and interactive links (CTAs) for external viewing. This component integrates the new 'build_cta.py' logic to generate interactive webview links and editor-specific deep links, expanding its role from simple reporting to providing actionable CI/CD report navigation. It receives processed data from the Visual Diff Generator to finalize the GitHub Action output.", "key_entities": [ { "qualified_name": "scripts.build_cta.main", @@ -545,22 +545,31 @@ ], "components_relations": [ { - "relation": "Supplies validated JSON analysis files to", - "src_name": "Engine Orchestrator", - "dst_name": "Visual Diff Engine", + "relation": "Passes validated analysis artifacts to initiate structural comparison", + "src_name": "Analysis Orchestrator", + "dst_name": "Visual Diff Generator", "src_id": "1", "dst_id": "2", "edge_count": 0, "is_static": false }, { - "relation": "Provides structural context and identified changes to", - "src_name": "Visual Diff Engine", - "dst_name": "Engagement & Feedback Manager", + "relation": "Provides context of changed files and components for deep-link generation", + "src_name": "Visual Diff Generator", + "dst_name": "UX & Integration Helper", "src_id": "2", "dst_id": "3", "edge_count": 0, "is_static": false + }, + { + "relation": "Returns formatted markdown snippets and CTA links for final output", + "src_name": "UX & Integration Helper", + "dst_name": "Analysis Orchestrator", + "src_id": "3", + "dst_id": "1", + "edge_count": 0, + "is_static": false } ] } \ No newline at end of file diff --git a/.codeboarding/health/health_report.json b/.codeboarding/health/health_report.json index 8d2b607..5dc4c6e 100644 --- a/.codeboarding/health/health_report.json +++ b/.codeboarding/health/health_report.json @@ -1,13 +1,13 @@ { "repository_name": "CodeBoarding-action", - "timestamp": "2026-06-11T16:35:41.927521+00:00", - "overall_score": 0.999609375, + "timestamp": "2026-06-11T16:41:11.294364+00:00", + "overall_score": 0.9996183206106869, "check_summaries": [ { "check_name": "function_size", "description": "Checks that functions/methods do not exceed line count thresholds", "check_type": "standard", - "total_entities_checked": 42, + "total_entities_checked": 43, "findings_count": 0, "warning_count": 0, "score": 1.0, @@ -17,7 +17,7 @@ "check_name": "fan_out", "description": "Checks efferent coupling: how many other functions each function calls", "check_type": "standard", - "total_entities_checked": 42, + "total_entities_checked": 43, "findings_count": 0, "warning_count": 0, "score": 1.0, @@ -27,7 +27,7 @@ "check_name": "fan_in", "description": "Checks afferent coupling: how many other functions call each function", "check_type": "standard", - "total_entities_checked": 42, + "total_entities_checked": 43, "findings_count": 0, "warning_count": 0, "score": 1.0, @@ -43,24 +43,6 @@ "score": 1.0, "finding_groups": [] }, - { - "check_name": "circular_dependencies", - "description": "Detects circular dependencies between packages", - "check_type": "circular_dependencies", - "cycles": [], - "packages_checked": 1, - "packages_in_cycles": 0 - }, - { - "check_name": "package_instability", - "description": "Computes Martin's instability metric (I = Ce / (Ca + Ce)) per package", - "check_type": "standard", - "total_entities_checked": 0, - "findings_count": 0, - "warning_count": 0, - "score": 1.0, - "finding_groups": [] - }, { "check_name": "unused_code_diagnostics", "description": "Detects unused imports, variables, functions, and dead code via LSP diagnostics",