The CLI command layer bypasses the forge adapter and branches on provider inline, contradicting the repo's own architecture rule and creating a second dispatch path that must be updated for every new forge (a direct tax on adding Gitea/Forgejo or any future forge). The workstation already does this correctly via useForgeAdapter. As of v0.81.0 (main @ 6de37e0).
AGENTS.md states the rule verbatim:
Forge work goes through the adapter. Add capabilities to the ForgeActions type in src/git/forgeActions.ts and implement for every provider (or return an explicit "unsupported on <forge>" result). Never branch on provider inside a surface.
Current state
src/commands/prCreate/handler.ts branches on provider directly at line 37 (if (provider !== 'github' && provider !== 'gitlab' && provider !== 'bitbucket')) and again at lines 162-182 for the create/open paths — despite createPullRequest and openPullRequest existing on ForgeActions (src/git/forgeActions.ts:148-149).
src/commands/prs/handler.ts and src/commands/issues/handler.ts hand-dispatch in their fetch lambdas: provider === 'gitlab' ? getMergeRequestList(...) : provider === 'bitbucket' ? getBitbucketPullRequestList(...) : getPullRequestList(...) — duplicating the dispatch that getPullRequestList/getIssueList on the adapter already encode (src/git/forgeActions.ts:117-118).
src/commands/utils/githubListCommand.ts:166 also special-cases gitlab for the noun label (arguably fine as presentation, but the label could come from the adapter/forge descriptor too).
- The TUI-side precedent:
src/workstation/runtime/hooks/useForgeAdapter.ts consumes getForgeActions and never sees provider ids.
Proposal
prCreate/handler.ts: replace the provider guards and per-provider create/open calls with getForgeActions(...)'s createPullRequest/openPullRequest; unsupported forges surface via the adapter's explicit unsupported result instead of an up-front allowlist.
prs/issues: change createGitHubListHandler's fetch contract to take the forge adapter (or move list-fetch dispatch fully behind getPullRequestList/getIssueList on the adapter) so the per-command spec only maps argv -> filter.
- Optionally add a forge display descriptor (noun/labels) to the adapter so
githubListCommand.ts drops its gitlab special case.
- Adapter-dispatch coverage exists as a template:
src/git/forgeDispatch.test.ts proves per-forge wiring — extend it for the command-layer paths.
Affected files
src/commands/prCreate/handler.ts
src/commands/prs/handler.ts, src/commands/issues/handler.ts
src/commands/utils/githubListCommand.ts
src/git/forgeActions.ts (possible descriptor addition)
Effort
M — the adapter methods already exist for all three forges; the work is inverting the command-layer call shape and preserving current error copy/exit codes.
Open questions
- Should
createGitHubListHandler receive the adapter object or just adapter-backed fetch functions?
- Is the gitlab "merge request" noun a presentation concern (keep in commands) or a forge property (move to adapter)?
The CLI command layer bypasses the forge adapter and branches on provider inline, contradicting the repo's own architecture rule and creating a second dispatch path that must be updated for every new forge (a direct tax on adding Gitea/Forgejo or any future forge). The workstation already does this correctly via
useForgeAdapter. As of v0.81.0 (main @ 6de37e0).AGENTS.md states the rule verbatim:
Current state
src/commands/prCreate/handler.tsbranches on provider directly at line 37 (if (provider !== 'github' && provider !== 'gitlab' && provider !== 'bitbucket')) and again at lines 162-182 for the create/open paths — despitecreatePullRequestandopenPullRequestexisting onForgeActions(src/git/forgeActions.ts:148-149).src/commands/prs/handler.tsandsrc/commands/issues/handler.tshand-dispatch in theirfetchlambdas:provider === 'gitlab' ? getMergeRequestList(...) : provider === 'bitbucket' ? getBitbucketPullRequestList(...) : getPullRequestList(...)— duplicating the dispatch thatgetPullRequestList/getIssueListon the adapter already encode (src/git/forgeActions.ts:117-118).src/commands/utils/githubListCommand.ts:166also special-cases gitlab for the noun label (arguably fine as presentation, but the label could come from the adapter/forge descriptor too).src/workstation/runtime/hooks/useForgeAdapter.tsconsumesgetForgeActionsand never sees provider ids.Proposal
prCreate/handler.ts: replace the provider guards and per-provider create/open calls withgetForgeActions(...)'screatePullRequest/openPullRequest; unsupported forges surface via the adapter's explicit unsupported result instead of an up-front allowlist.prs/issues: changecreateGitHubListHandler'sfetchcontract to take the forge adapter (or move list-fetch dispatch fully behindgetPullRequestList/getIssueListon the adapter) so the per-command spec only maps argv -> filter.githubListCommand.tsdrops its gitlab special case.src/git/forgeDispatch.test.tsproves per-forge wiring — extend it for the command-layer paths.Affected files
src/commands/prCreate/handler.tssrc/commands/prs/handler.ts,src/commands/issues/handler.tssrc/commands/utils/githubListCommand.tssrc/git/forgeActions.ts(possible descriptor addition)Effort
M — the adapter methods already exist for all three forges; the work is inverting the command-layer call shape and preserving current error copy/exit codes.
Open questions
createGitHubListHandlerreceive the adapter object or just adapter-backed fetch functions?