Summary
PR #1104 lands forge-agnostic outbound CRUD (create PR / view issue / add review comment / merge / etc.) for Gitea and GitLab via a single 822-line .archon/scripts/forge-cli.ts script. That unblocks the feature, but it sits parallel to the existing packages/adapters/src/community/forge/{gitea,gitlab}/adapter.ts files which already handle inbound webhooks for the same forges.
This issue tracks the architectural cleanup: migrate the outbound forge logic from the script into the existing community adapters so we have one extension point per forge rather than two.
This is not a regression risk — forge-cli.ts works and has tests. It's about consolidating the pattern before more forges (Bitbucket, Sourcehut, etc.) calcify the parallel structure.
Why this matters
direction.md (updated alongside this issue) commits to forge-agnostic support with the long-term home being community adapters. Today after #1104 lands we have:
| Operation |
Today's home |
Inbound webhooks (handleWebhook) |
packages/adapters/src/community/forge/{gitea,gitlab}/adapter.ts ✅ |
sendMessage (post comment) |
Same adapter ✅ |
| Outbound CRUD (create-pr, view-issue, etc.) |
.archon/scripts/forge-cli.ts ❌ — separate script |
Adding a new forge today means: implement an adapter (inbound + sendMessage) AND extend forge-cli.ts (outbound) AND wire bundled commands. After this refactor: extend the existing adapter only.
Scope
Define the outbound interface
Either extend IPlatformAdapter (in @archon/core) with optional outbound methods, or sibling-it as IForgeOperations if IPlatformAdapter would get bloated. Methods (deriving from forge-cli.ts's commands):
createPullRequest({ title, body, base, head, draft }) → { number, url }
viewIssue({ owner, repo, number }) → IssueView
viewPullRequest({ owner, repo, number }) → PullRequestView
addReviewComment({ owner, repo, prNumber, body, ... })
mergePullRequest({ owner, repo, number, method })
getDiff({ owner, repo, prNumber }) → string
setLabels({ owner, repo, number, labels })
- (full method list to be derived from
forge-cli.ts's top-level case statements)
Implement on each adapter
packages/adapters/src/forge/github/adapter.ts — uses Octokit (already present for sendMessage).
packages/adapters/src/community/forge/gitea/adapter.ts — Gitea API client.
packages/adapters/src/community/forge/gitlab/adapter.ts — GitLab API client.
Each forge owns its API quirks (e.g., GitLab's draft-MR title prefix, Gitea's raw_diffs endpoint — both already noted in #1104's commit messages) inside its own adapter.
Re-wire bundled commands
.archon/commands/defaults/archon-create-pr.md, archon-finalize-pr.md, archon-investigate-issue.md, etc. (25 files in #1104) currently call into forge-cli.ts via bash. After this refactor, they call into the adapter abstraction — likely via a workflow variable like $forge.create_pr or a tiny shim.
Reduce forge-cli.ts
Should drop to ~0 lines. The shared interface code may move to packages/adapters/src/forge/types.ts.
Acceptance criteria
Why we're not blocking #1104 on this
@truck0321 has been waiting 8+ days for the direction call and the feature is clean and well-tested. Asking for the architectural refactor before merge would be 2-3 hours of additional work on top of an already-rebased branch. Pragmatic move: ship the feature, track the refactor here, and migrate when someone has time (could be the original contributor, could be a maintainer pass).
References
Scope
- Package(s) likely involved:
adapters (community/forge/{gitea,gitlab,github}), core (interface definition), workflows (bundled command edits), bundled defaults regeneration.
Summary
PR #1104 lands forge-agnostic outbound CRUD (create PR / view issue / add review comment / merge / etc.) for Gitea and GitLab via a single 822-line
.archon/scripts/forge-cli.tsscript. That unblocks the feature, but it sits parallel to the existingpackages/adapters/src/community/forge/{gitea,gitlab}/adapter.tsfiles which already handle inbound webhooks for the same forges.This issue tracks the architectural cleanup: migrate the outbound forge logic from the script into the existing community adapters so we have one extension point per forge rather than two.
This is not a regression risk —
forge-cli.tsworks and has tests. It's about consolidating the pattern before more forges (Bitbucket, Sourcehut, etc.) calcify the parallel structure.Why this matters
direction.md(updated alongside this issue) commits to forge-agnostic support with the long-term home being community adapters. Today after #1104 lands we have:handleWebhook)packages/adapters/src/community/forge/{gitea,gitlab}/adapter.ts✅sendMessage(post comment).archon/scripts/forge-cli.ts❌ — separate scriptAdding a new forge today means: implement an adapter (inbound + sendMessage) AND extend
forge-cli.ts(outbound) AND wire bundled commands. After this refactor: extend the existing adapter only.Scope
Define the outbound interface
Either extend
IPlatformAdapter(in@archon/core) with optional outbound methods, or sibling-it asIForgeOperationsifIPlatformAdapterwould get bloated. Methods (deriving fromforge-cli.ts's commands):createPullRequest({ title, body, base, head, draft }) → { number, url }viewIssue({ owner, repo, number }) → IssueViewviewPullRequest({ owner, repo, number }) → PullRequestViewaddReviewComment({ owner, repo, prNumber, body, ... })mergePullRequest({ owner, repo, number, method })getDiff({ owner, repo, prNumber }) → stringsetLabels({ owner, repo, number, labels })forge-cli.ts's top-level case statements)Implement on each adapter
packages/adapters/src/forge/github/adapter.ts— uses Octokit (already present forsendMessage).packages/adapters/src/community/forge/gitea/adapter.ts— Gitea API client.packages/adapters/src/community/forge/gitlab/adapter.ts— GitLab API client.Each forge owns its API quirks (e.g., GitLab's draft-MR title prefix, Gitea's
raw_diffsendpoint — both already noted in #1104's commit messages) inside its own adapter.Re-wire bundled commands
.archon/commands/defaults/archon-create-pr.md,archon-finalize-pr.md,archon-investigate-issue.md, etc. (25 files in #1104) currently call intoforge-cli.tsvia bash. After this refactor, they call into the adapter abstraction — likely via a workflow variable like$forge.create_pror a tiny shim.Reduce
forge-cli.tsShould drop to ~0 lines. The shared interface code may move to
packages/adapters/src/forge/types.ts.Acceptance criteria
forge.test.ts(110 lines, added in feat: forge-agnostic workflow commands (Gitea/GitLab support) #1104) still passes — likely with imports updated.community/forge/<name>/adapter.tsimplementing the outbound interface. No central script edit.forge-cli.tsis removed (or reduced to a thin wrapper if it's still useful as a CLI entry point for end-users).direction.mdclause about forge support stays accurate after the refactor.Why we're not blocking #1104 on this
@truck0321 has been waiting 8+ days for the direction call and the feature is clean and well-tested. Asking for the architectural refactor before merge would be 2-3 hours of additional work on top of an already-rebased branch. Pragmatic move: ship the feature, track the refactor here, and migrate when someone has time (could be the original contributor, could be a maintainer pass).
References
forge-cli.ts)packages/adapters/src/community/forge/README.mdpackages/adapters/src/forge/github/adapter.tsdirection.md"Forge-agnostic" clauseScope
adapters(community/forge/{gitea,gitlab,github}),core(interface definition),workflows(bundled command edits), bundled defaults regeneration.