Replace tea with forgejo-api in os-backlog #856
Labels
No labels
P0
P1
P2
P3
bug
category/code-change
category/cross-repo
category/eval-run
category/investigation
category/no-test-target
category/owner-ruling
category/prose-artifact
category/recurring-chore
category/structure
category/umbrella
crc-refactor
create
delete
enhancement
filed-by/agent
filed-by/user
frozen
lint-rule
needs-info
needs-triage
next
plugin/cc-architect
plugin/os
plugin/os-adr
plugin/os-aidd-lint
plugin/os-backlog
plugin/os-context
plugin/os-doc-hygiene
plugin/os-sdlc
plugin/os-vault
project/cc-os
ready-for-agent
ready-for-human
recurring
review
rule-proposal
update
waiting
wayfinder:grilling
wayfinder:map
wayfinder:map
wayfinder:research
wayfinder:task
wayfinder:task
wontfix
worklist/deviations
worklist/lint-rule
worklist/new-implement-build
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
jared/cc-os#856
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
os-backlog runs the
teaCLI for every Forgejo operation.Ticket titles, bodies, and comments go through argv.
tea issue createprints text, soCommandRunnerextracts the number and URL with a regex.ADR-0256 replaces tea with an in-process Forgejo REST client.
ADR-0257 names that client: the
forgejo-apigem is the only Forgejo client.tea_backend.rbhas 14 tea command shapes in total: create, labels list, issues list, issue view, comment, close, comment ls, label add, label remove, assign, unassign, and edit.Reproduce
n/a: design change from ADR-0256 and ADR-0257.
Approach
A new
Backlog::ForgejoBackendkeeps the public contract ofTeaBackend(same methods, same argument order withrepo_slugfirst, same return shapes) and delegates each call to::Forgejo::Client.Callers and
GhBackenddo not change.Issues#routebuilds it forforgejo:trackers.The client is an injected
client:keyword.The default is built lazily on first Forgejo call:
::Forgejo::Client.new(url: "https://forgejo.swansoncloud.com", api_key: ENV.fetch("FORGEJO_TOKEN")).A
github:orrepo:tracker never needsFORGEJO_TOKEN.IssueShow,InspectCommand, andbin/wakeup-pollread through the same backend.TeaBackendand its tea argv builders are deleted.Method mapping (
repo = client.repo(owner, name), from theowner/nameslug):create(slug, title:, body:, labels:)callsrepo.issues.create(title:, body:, labels: [ids])and returns{ number: issue.number, url: issue.html_url }.labels_list(slug)readsrepo.labels.listand returns[{ name:, description: }].list(slug)readsrepo.issues.list(state: "open", type: "issues")(drain the lazy page withto_a) and returns{ number:, title:, labels: [names], assignee: }rows.labels(slug, n)andfetch(slug, n)readrepo.issues.get(n).commentcallsrepo.comments.create(n, body:).commentsmapsrepo.comments.list_for_issue(n)to bodies.closecallsrepo.issues.update(n, state: "closed").closed?readsrepo.issues.get(n).add_labelandremove_labelcallrepo.issues.labels(n).add(labels: [id])and.remove(id).assignandunassigncallrepo.issues.update(n, assignees: [name])andassignees: [].edit(slug, n, title, body)callsrepo.issues.update(n, body:)and addstitle:only when a title is given.repo.labels.list. An unknown name raisesIssues::CommandFailedthat names the label.open_issues(slug)returns{ number:, title:, labels: [names], body:, url: }rows for open issues.InspectCommandandbin/wakeup-polluse it.::Forgejo::Errorand raisesIssues::CommandFailedwith the error message.::Forgejo::ApiError#statusand the message carry the HTTP status. Callers already rescueCommandFailed.Boot (ADR-0166), modeled on
plugins/os-sdlc: os-backlog gets its own Gemfile and a committed Gemfile.lock.The single boot file is
plugins/os-backlog/lib/backlog.rb.It sets
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)and requiresbundler/setupbeforerequire 'zeitwerk'.plugins/os-backlog/bin/os-backlog,bin/wakeup-poll,bin/decision-sweep, andtests/test_helper.rbalready load that file, so they need no other boot line.The Gemfile is modeled on
plugins/os-sdlc/Gemfile:ruby "4.0.2",thor,zeitwerk, theforgejo-apiline from ADR-0257, andminitestin the:development, :testgroup.Test seam (same lesson as #858, commit
67d32857): tests inject a fake client and start no process and no network.Model the fakes on
plugins/os-sdlc/tests/support/fake_forgejo_client.rbandplugins/os-sdlc/tests/support/fake_forgejo_repo.rb.Create os-backlog copies under
plugins/os-backlog/tests/support/, with the extra repo methods this backend calls (issue create/list/update, comment list, label list/remove/replace).Backlog::CLI.starttakes an optionalclient:in its config hash, next torunner:, and passes it toIssues,IssueShow, andInspectCommand.bin/os-backlogdoes not pass it.os-backlog tests use
parallelize_me!(threads), notminitest-parallel_fork, so no Marshal issue applies.tests/test_helper.rbstill requiresforgejoso that tests can name::Forgejo::NotFoundErrorwithout an autoload race.Required work:
plugins/os-backlog/Gemfileand a committedplugins/os-backlog/Gemfile.lockwith theforgejo-apiline from ADR-0257, and load them inplugins/os-backlog/lib/backlog.rbas described in the Boot paragraph. check: presentbundler/setupinplugins/os-backlog/lib/backlog.rbplugins/os-backlog/lib/backlog/forgejo_backend.rbwith the method mapping above. check: present::Forgejo::Clientinplugins/os-backlog/lib/backlog/forgejo_backend.rbforgejo:trackers toForgejoBackendinplugins/os-backlog/lib/backlog/issues.rb(rename the@tea_backendivar and thebackends[:tea]key) and add theclient:keyword throughIssues,Backlog::CLI.start, andcli_command_helpers.rb. check: absentTeaBackendinplugins/os-backlog/lib/backlog/issues.rbplugins/os-backlog/lib/backlog/tea_backend.rb. IfCommandRunnerhas no caller left exceptGhBackend, keep it forGhBackend; drop only the tea-specific parts. check: absentclass TeaBackendinplugins/os-backlog/lib/backlog/*.rbplugins/os-backlog/lib/backlog/issue_show.rbthrough the backend or the injected client and remove itsteaargv and the tea comment. check: absent"tea"inplugins/os-backlog/lib/backlog/issue_show.rbplugins/os-backlog/lib/backlog/inspect_command.rb, replace the tea methods with a call toopen_issues. Removetea_unavailable_resultand the line-count fallback. Report"tool" => "forgejo". A::Forgejo::Errorgives"open_count" => niland a"reason". check: absent"tea"inplugins/os-backlog/lib/backlog/inspect_command.rbplugins/os-backlog/bin/wakeup-poll, list Forgejo issues throughopen_issuesand remove theteaargv and the "is tea logged in" text. check: absentteainplugins/os-backlog/bin/wakeup-pollplugins/os-backlog/tests/issue_show_forgejo_test.rbto the injected fake client (same three behaviors: normalized issue, error, no non-JSON case).plugins/os-backlog/tests/issues_create_test.rb,plugins/os-backlog/tests/issues_create_with_labels_test.rb,plugins/os-backlog/tests/issues_list_test.rb,plugins/os-backlog/tests/issues_scope_labels_test.rb, andplugins/os-backlog/tests/issues_close_protocol_test.rbto the injected fake client. Keep theirgithub:andrepo:tests unchanged.plugins/os-backlog/tests/inspect_command_tea_json_test.rbandplugins/os-backlog/tests/inspect_command_executable_path_test.rbto the injected fake client. TheExecutablePathtests keep only whatghstill needs: noteaCLI not found case, and the no-whichtest stays.plugins/os-backlog/tests/issue_edit_cli_test.rb,plugins/os-backlog/tests/labels_cli_contract_test.rb,plugins/os-backlog/tests/issue_create_body_file_cli_test.rb,plugins/os-backlog/tests/issue_create_scope_gate_cli_test.rb,plugins/os-backlog/tests/issue_create_origin_gate_cli_test.rb, andplugins/os-backlog/tests/issue_create_template_gate_cli_test.rbfrom a faketeaon PATH to in-processBacklog::CLI.start(..., client: fake)(the pattern ofplugins/os-backlog/tests/cli_issue_create_scope_gate_test.rb). Assert the body, title, and label IDs that reach the fake repo. Keep thegithub:andrepo:cases unchanged.FAKE_TEA_*andinstall_fake_tea*andwith_forgejo_trackertea wiring inplugins/os-backlog/tests/support/labels_cli_helpers.rb,plugins/os-backlog/tests/support/inspect_fake_tea_json_runner.rb,plugins/os-backlog/tests/support/inspect_fake_tea_fallback_runner.rb, andplugins/os-backlog/tests/support/inspect_recording_tea_runner.rb. Keepplugins/os-backlog/tests/support/no_real_tracker_commands.rbandplugins/os-backlog/tests/shell_runner_refuses_tracker_commands_test.rbunchanged:ghstill uses the guard. check: absentinstall_fake_teainplugins/os-backlog/tests/support/labels_cli_helpers.rbissue-createcreates the issue viateaso that it says the Forgejo API client. check: presentForgejo API clientinplugins/os-backlog/skills/capture/SKILL.mdRejected: keep tea and add
--output jsonwhere it exists.tea issues createhas none (ADR-0256).Rejected: a second Forgejo client such as
forgejo-client. ADR-0257 makesforgejo-apithe only one.Acceptance criteria
forgejo:tracker and a fake client, whenissue-createruns with a body that starts with--label, then the fake repo receives that body unchanged and the command prints the new number and URL.forgejo:tracker, whenissue-show,comment,close,issue-edit, and label add/remove run, then each one calls the matchingrepo.issuesorrepo.commentsmethod on the client and noteaprocess starts.FORGEJO_TOKENis not set and no client is injected, when any Forgejo command runs, then it exits non-zero with a message that namesFORGEJO_TOKEN.::Forgejo::ApiError, when a command runs, then the command prints the error message (it starts with the HTTP status) and exits non-zero.wakeup-pollruns for aforgejo:project, when open issues exist, then it lists them with number, title, labels, body, and URL, as it does today.inspectruns on a Forgejo repository, when open issues exist, then it reports their count with"tool" => "forgejo", and no line-count fallback exists.github:orrepo:tracker, when any command runs withoutFORGEJO_TOKEN, then it behaves as before.ruby plugins/os-backlog/tests/all.rb(theos-backlogtarget in.sdlc/project.yaml) passes.Owner sketch
Non-goals
gh). Its move to octokit is #857.tea labels createcommands inplugins/os-backlog/skills/route/SKILL.md.ForgejoBackendhas no label-create method in this ticket.teaCLI" line is outside the os-backlog target (ADR-0255); the orchestrator updates it by hand when this ticket closes.Blocked by
forgejo-apigem, ADR-0257) is closed. The git-gem dependency work is not a blocker.Retired tests
plugins/os-backlog/tests/inspect_command_tea_fallback_test.rb
Origin
Live evidence, 2026-09-30:
os-backlog issue-createfor #855 printed"url": "https://<host>/api/v1/....". The regex that readstea issue createoutput matched a URL inside the ticket body, not the created issue URL. Acceptance criterion 1 covers this.Update 2026-09-30: the Forgejo client is now the generated
forgejo-apigem (ADR-0257, commitb68750db), notforgejo-client. Source:gem "forgejo-api", git: "ssh://git@forgejo.swansoncloud.com:2222/jared/forgejo-api.git", require: "forgejo". Client:Forgejo::Client.new(url: "https://forgejo.swansoncloud.com", api_key: ENV.fetch("FORGEJO_TOKEN"));repo = client.repo(owner, name); issuesrepo.issues.create/get/list/update, commentsrepo.comments.create(n, body:), issue labelsrepo.issues.labels(n).add(labels: [ids])/.remove(id)/.replace, repo labelsrepo.labels.list. Label operations take IDs, so the backend resolves names throughrepo.labels.list. os-backlog has no Gemfile yet; ADR-0166 already decides the loading method (per-plugin Gemfile + lockfile + one boot file), so this ticket's first step adds it. The git-gem work is not a blocker for this ticket.Replace tea with forgejo-client in os-backlogto Replace tea with forgejo-api in os-backlogWork started 2026-09-30 on branch ticket-856 (worktree .claude/worktrees/ticket-856), os-sdlc pipeline, target os-backlog.
--label check 2026-09-30: this comment was posted through the new ForgejoBackend (forgejo-api, worktree ticket-856); a body that starts with --label is stored unchanged.
Resolution
Done: os-backlog's Forgejo backend is ForgejoBackend over forgejo-api in process (ADR-0257); TeaBackend deleted; issue-show, inspect and wakeup-poll read through it; os-backlog has a Gemfile/lockfile and fails loudly with a bundle-install hint when gems are missing; CLAUDE.md now says to use the os-backlog CLI.
Evidence: main
fc9ba80e. os-backlog suite 286 runs, 0 failures, 0 errors. Live on forgejo.swansoncloud.com through the worktree CLI: issue-show 856 (labels as names), labels (10), comment with a body starting with --label posted, issue-show 999999 -> HTTP 404, missing FORGEJO_TOKEN named. Installed plugin after merge: bundle check OK, issue-show 856 OK, and this close ran through it. Implementation 18 escalated at programmer-repair (stale tea tests, #863); hand-finished, including a nested-Hash label bug the fakes had hidden.Follow-ups: #863 no pipeline role migrates existing tests (skeptic CREATE). #857 gh to octokit (existing). os-backlog has no rubocop in its bundle and no lint_config in .sdlc/project.yaml; 23 Metrics offenses exist under the system rubocop: dropped, lint is not gated for this target. Each machine needs bundle install in plugins/os-backlog. The route skill still documents tea labels create (Non-goal of this ticket): dropped here, belongs with a label-create backend method when needed.