Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ The command needs the `issue_comment` trigger and runs from your default branch
| `parsing_model` | `google/gemini-3.1-flash-lite-preview` | Parsing model. OpenRouter default shown; other providers use their own engine default. |
| `comment_header` | `Architecture review` | Heading for the PR comment. |
| `trigger_command` | `/codeboarding` | Slash command for trusted on-demand runs. |
| `cta_base_url` | empty | Optional click-proxy base URL for editor and extension links. |
| `cta_base_url` | empty | Click-proxy base URL for the editor/extension links (tracks owner/repo/pr). Empty links straight to the editor deep link and Marketplace. |

## Outputs

Expand Down
6 changes: 3 additions & 3 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ inputs:
required: false
default: '1'
cta_base_url:
description: 'Base URL of the click proxy (e.g. https://go.codeboarding.org). When set, the comment adds "open in VS Code/Cursor" / "get the extension" links with owner/repo/pr appended. Empty disables the CTA.'
description: 'Base URL of the click proxy (e.g. https://go.codeboarding.org) for the "open in VS Code/Cursor" / "get the extension" links, so owner/repo/pr are tracked. Empty (default) links straight to the editor deep link and Marketplace instead.'
required: false
default: ''
trigger_command:
Expand Down Expand Up @@ -573,8 +573,8 @@ runs:
else echo "$N components changed"; fi
}

# CTA footer (editor + extension links via the click proxy, warning banner
# on real health findings). build_cta also emits the ⚠️ banner with no proxy.
# CTA footer: editor + extension links (via the click proxy when CTA_BASE is
# set, else straight to the editor deep link / Marketplace) plus the ⚠️ banner.
cta() {
python3 "$ACTION_PATH/scripts/build_cta.py" \
--cta-base "$CTA_BASE" --owner "$OWNER" --repo "$REPO" --pr "$PR" \
Expand Down
39 changes: 25 additions & 14 deletions scripts/build_cta.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
"""Build the call-to-action footer appended to the architecture-diff PR comment.

The footer links into CodeBoarding's click proxy (so owner/repo/pr are tracked)
and currently drives straight to the VS Code/Cursor **extension**: an "open this
The footer drives straight to the VS Code/Cursor **extension**: an "open this
architecture in your editor" link (editor-specific) plus an "install the
extension" link, and a warning banner when real health findings exist. A
extension" link, and a warning banner when real health findings exist. When a
click proxy (``cta_base``) is set the links route through it so owner/repo/pr are
tracked; otherwise they point at the final destinations directly — the editor
``<scheme>:extension/...`` deep link and the Marketplace listing. A
no-install hosted-webview ("explore in browser") tier is intentionally deferred
(see docs/COMMIT_STRATEGY.md) — the committed analysis already supports it later.

Expand Down Expand Up @@ -45,33 +47,42 @@ def detect_editors(repo_path: Path) -> list[str]:

_EDITOR_LABEL = {"vscode": "VS Code", "cursor": "Cursor"}

# No-proxy fallback targets: the final destinations the click proxy would route to.
_EXTENSION_ID = "Codeboarding.codeboarding"
_EDITOR_DEEPLINK = {e: f"{e}:extension/{_EXTENSION_ID}" for e in _EDITOR_LABEL}
_MARKETPLACE_URL = f"https://marketplace.visualstudio.com/items?itemName={_EXTENSION_ID}"


def build_cta(cta_base: str, owner: str, repo: str, pr: str, repo_path: Path, issues: int = 0) -> str:
"""Return the markdown CTA footer (the warning banner shows even without a proxy URL).
"""Return the markdown CTA footer: a health-warning banner plus editor/extension links.

The ⚠️ health banner is informational and needs no proxy, so it renders
whenever ``issues > 0``; the editor/marketplace links require ``cta_base``.
Returns '' only when there's nothing to show.
With a ``cta_base`` proxy the links route through it (owner/repo/pr tracked);
without one they point straight to the destinations the proxy would route to —
the editor's ``<scheme>:extension/...`` deep link and the Marketplace listing.
The ⚠️ banner shows whenever ``issues > 0``.
"""
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.")

editors = detect_editors(repo_path)
if cta_base:
base = cta_base.rstrip("/")

def link(path: str, **extra: str) -> str:
return f"{base}/{path}?" + urlencode({"owner": owner, "repo": repo, "pr": pr, **extra})

editor_links = " · ".join(
f"[**Open in {_EDITOR_LABEL[e]} →**]({link('open-in-editor', editor=e)})" for e in detect_editors(repo_path)
)
parts.append(f"See this architecture in your editor: {editor_links}")
parts.append(f"💡 New to CodeBoarding? [**Get the extension →**]({link('use-marketplace')})")
editor_href = {e: link("open-in-editor", editor=e) for e in editors}
extension_href = link("use-marketplace")
else:
editor_href = {e: _EDITOR_DEEPLINK[e] for e in editors}
extension_href = _MARKETPLACE_URL

editor_links = " · ".join(f"[**Open in {_EDITOR_LABEL[e]} →**]({editor_href[e]})" for e in editors)
parts.append(f"See this architecture in your editor: {editor_links}")
parts.append(f"💡 New to CodeBoarding? [**Get the extension →**]({extension_href})")

if not parts:
return ""
lines = ["", "---"]
for p in parts:
lines += ["", p]
Expand Down
17 changes: 11 additions & 6 deletions tests/test_build_cta.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,18 @@ def test_both_vscode_first(self):


class TestBuildCta(unittest.TestCase):
def test_empty_base_yields_no_footer(self):
self.assertEqual(bc.build_cta("", "o", "r", "1", repo_with()), "")

def test_warning_shows_without_cta_base(self):
out = bc.build_cta("", "o", "r", "1", repo_with(), issues=3)
def test_no_proxy_falls_back_to_editor_deeplink_and_marketplace(self):
out = bc.build_cta("", "o", "r", "1", repo_with(".cursor"), issues=3)
self.assertIn("3 architecture issues found", out)
self.assertNotIn("http", out) # no links without a proxy base
self.assertIn("[**Open in Cursor →**](cursor:extension/Codeboarding.codeboarding)", out)
self.assertIn("marketplace.visualstudio.com/items?itemName=Codeboarding.codeboarding", out)
self.assertNotIn("open-in-editor", out) # no proxy routing
self.assertNotIn("Open in VS Code", out) # cursor-only repo

def test_no_proxy_default_vscode_deeplink_no_banner_at_zero(self):
out = bc.build_cta("", "o", "r", "1", repo_with()) # neither dir, no issues
self.assertIn("[**Open in VS Code →**](vscode:extension/Codeboarding.codeboarding)", out)
self.assertNotIn("architecture issue", out) # banner suppressed at 0 issues

def test_links_banner_and_cursor_only(self):
out = bc.build_cta("https://x.dev/", "Org", "Repo", "9", repo_with(".cursor"), issues=2)
Expand Down
Loading