Replace tea and gh with forgejo-api and octokit in os-sdlc #858

Closed
opened 2026-09-30 18:14:38 +00:00 by jared · 4 comments
Owner

Problem

os-sdlc runs tea and gh to read tickets and to mark them blocked. It also runs tea logins list and searches the text for a host name to decide if a remote is Forgejo. ADR-0256 replaces tea with the forgejo-api gem (ADR-0257) and gh with octokit.

plugins/os-sdlc/lib/os_sdlc/adapter/issue_source/forgejo.rb:9           tea issues N --repo S -o json
plugins/os-sdlc/lib/os_sdlc/adapter/issue_source/github.rb:9            gh issue view N --repo S --json title,body
plugins/os-sdlc/lib/os_sdlc/adapter/issue_source/tracker_resolver.rb:206   tea logins list   (out.include?(host))
plugins/os-sdlc/lib/os_sdlc/adapter/tracker/tea.rb:28                   tea issues edit N --add-labels waiting --repo S
plugins/os-sdlc/lib/os_sdlc/adapter/tracker/tea.rb:37                   tea comment --repo S N MSG
plugins/os-sdlc/lib/os_sdlc/adapter/tracker/tea/body_fetch.rb:20        <os-backlog bin> issue-show N

Reproduce

n/a: design change from ADR-0256.

Approach

IssueSource::Forgejo#fetch calls repo.issues.get(n) and IssueSource::Github#fetch calls Octokit::Client#issue. Both still return "title\n\nbody". Tracker::Tea becomes Tracker::Forgejo: add_waiting_label resolves the waiting label ID through repo.labels.list and calls repo.issues.labels(n).add(labels: [id]), blocker_comment calls repo.comments.create(n, body: message), and fetch_body calls repo.issues.get(n) in place of the os-backlog CLI. Label operations take numeric IDs, so resolve the ID from the name; do not pass label names. TrackerResolver#forgejo_host? compares the remote host with the Forgejo host constant, the same rule os-backlog uses (forgejo.swansoncloud.com), in place of tea logins list. Tokens come from ENV.fetch("FORGEJO_TOKEN") and ENV.fetch("GITHUB_TOKEN").

The gem is forgejo-api (ADR-0257), the only Forgejo client. Build it with ::Forgejo::Client.new(url: "https://forgejo.swansoncloud.com", api_key: ENV.fetch("FORGEJO_TOKEN")), then repo = client.repo(owner, name) (no HTTP call). The gem module is top-level Forgejo, so code inside OsSdlc::IssueSource::Forgejo and OsSdlc::Tracker::Forgejo must write ::Forgejo::Client and ::Forgejo::NotFoundError; a bare Forgejo would find the nested class. Errors come from ::Forgejo::Error (NotFoundError for 404, AuthenticationError for 401).

Test seam: each owner receives the client (or a repo object) by injection, so tests pass a fake and never use the network. The default builds the real client from ENV.

Required work:

  1. Add gem "forgejo-api", git: "ssh://git@forgejo.swansoncloud.com:2222/jared/forgejo-api.git", require: "forgejo" and octokit to plugins/os-sdlc/Gemfile, and update plugins/os-sdlc/Gemfile.lock (ADR-0166). Each machine then needs bundle install.

Rejected: call the os-backlog CLI for all tracker reads — it keeps a shell call and couples os-sdlc to os-backlog output (ADR-0256).

Acceptance criteria

  1. Given a forgejo: tracker, when implementation-open reads a ticket, then it gets the title and body from the Forgejo API and no tea process starts.
  2. Given a github: tracker, when implementation-open reads a ticket, then it gets the title and body from the GitHub API and no gh process starts.
  3. Given an implementation fails, when the tracker marks the ticket blocked, then the ticket gets the waiting label (added by its numeric ID, resolved from the name) and a blocker comment through the Forgejo API.
  4. Given a remote on forgejo.swansoncloud.com and no tracker key, when the tracker resolves, then it resolves to forgejo:<owner>/<repo> without tea logins list.
  5. Given the needed token variable is not set, when a tracker call runs, then the error names that variable.
  6. Given a fake client is injected, when an owner fetches, labels, or comments, then no network request is made.
  7. Given the Gemfile change, when bundle install runs in plugins/os-sdlc, then Gemfile.lock lists forgejo-api and octokit, and bundle exec ruby -e 'require "forgejo"' succeeds.

Owner sketch

class OsSdlc::IssueSource::Forgejo
  def initialize(slug, client: default_client)   # default_client => ::Forgejo::Client.new(url:, api_key: ENV.fetch("FORGEJO_TOKEN"))
  def fetch(ticket_number)   # repo.issues.get(n) => "title\n\nbody"
end

class OsSdlc::Tracker::Forgejo
  def initialize(slug, client: default_client)   # repo = client.repo(owner, name)
  def add_waiting_label(id)        # repo.issues.labels(id).add(labels: [label_id("waiting")])
  def blocker_comment(id, message) # repo.comments.create(id, body: message)
  def fetch_body(id)               # repo.issues.get(id).body
  private def label_id(name)       # repo.labels.list.find { |l| l.name == name }.id
end

Non-goals

  • os-backlog's tea and gh calls. They have their own tickets.

Blocked by

  • #855 (closed; resolved by the forgejo-api gem, ADR-0257).

Origin

  • Trigger: #845 audit; ADR-0256 decided 2026-09-30.
  • Improvised this session: none
  • Chain: ticket reads and blocker marks shell out to tea/gh, and Forgejo detection searches tea logins list text ← DESIGN: ADR-0123, amended by ADR-0256.
  • Root candidate: this ticket
  • Where: plugins/os-sdlc/lib/os_sdlc/adapter/issue_source/, plugins/os-sdlc/lib/os_sdlc/adapter/tracker/tea.rb
  • Session: f090dd39-8ad6-4f4e-8743-7bbedb8f08d4
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/f090dd39-8ad6-4f4e-8743-7bbedb8f08d4.jsonl
## Problem os-sdlc runs `tea` and `gh` to read tickets and to mark them blocked. It also runs `tea logins list` and searches the text for a host name to decide if a remote is Forgejo. ADR-0256 replaces tea with the `forgejo-api` gem (ADR-0257) and gh with octokit. ``` plugins/os-sdlc/lib/os_sdlc/adapter/issue_source/forgejo.rb:9 tea issues N --repo S -o json plugins/os-sdlc/lib/os_sdlc/adapter/issue_source/github.rb:9 gh issue view N --repo S --json title,body plugins/os-sdlc/lib/os_sdlc/adapter/issue_source/tracker_resolver.rb:206 tea logins list (out.include?(host)) plugins/os-sdlc/lib/os_sdlc/adapter/tracker/tea.rb:28 tea issues edit N --add-labels waiting --repo S plugins/os-sdlc/lib/os_sdlc/adapter/tracker/tea.rb:37 tea comment --repo S N MSG plugins/os-sdlc/lib/os_sdlc/adapter/tracker/tea/body_fetch.rb:20 <os-backlog bin> issue-show N ``` ## Reproduce n/a: design change from ADR-0256. ## Approach `IssueSource::Forgejo#fetch` calls `repo.issues.get(n)` and `IssueSource::Github#fetch` calls `Octokit::Client#issue`. Both still return `"title\n\nbody"`. `Tracker::Tea` becomes `Tracker::Forgejo`: `add_waiting_label` resolves the `waiting` label ID through `repo.labels.list` and calls `repo.issues.labels(n).add(labels: [id])`, `blocker_comment` calls `repo.comments.create(n, body: message)`, and `fetch_body` calls `repo.issues.get(n)` in place of the os-backlog CLI. Label operations take numeric IDs, so resolve the ID from the name; do not pass label names. `TrackerResolver#forgejo_host?` compares the remote host with the Forgejo host constant, the same rule os-backlog uses (`forgejo.swansoncloud.com`), in place of `tea logins list`. Tokens come from `ENV.fetch("FORGEJO_TOKEN")` and `ENV.fetch("GITHUB_TOKEN")`. The gem is `forgejo-api` (ADR-0257), the only Forgejo client. Build it with `::Forgejo::Client.new(url: "https://forgejo.swansoncloud.com", api_key: ENV.fetch("FORGEJO_TOKEN"))`, then `repo = client.repo(owner, name)` (no HTTP call). The gem module is top-level `Forgejo`, so code inside `OsSdlc::IssueSource::Forgejo` and `OsSdlc::Tracker::Forgejo` must write `::Forgejo::Client` and `::Forgejo::NotFoundError`; a bare `Forgejo` would find the nested class. Errors come from `::Forgejo::Error` (`NotFoundError` for 404, `AuthenticationError` for 401). Test seam: each owner receives the client (or a repo object) by injection, so tests pass a fake and never use the network. The default builds the real client from ENV. Required work: 1. Add `gem "forgejo-api", git: "ssh://git@forgejo.swansoncloud.com:2222/jared/forgejo-api.git", require: "forgejo"` and `octokit` to `plugins/os-sdlc/Gemfile`, and update `plugins/os-sdlc/Gemfile.lock` (ADR-0166). Each machine then needs `bundle install`. Rejected: call the os-backlog CLI for all tracker reads — it keeps a shell call and couples os-sdlc to os-backlog output (ADR-0256). ## Acceptance criteria 1. Given a `forgejo:` tracker, when `implementation-open` reads a ticket, then it gets the title and body from the Forgejo API and no `tea` process starts. 2. Given a `github:` tracker, when `implementation-open` reads a ticket, then it gets the title and body from the GitHub API and no `gh` process starts. 3. Given an implementation fails, when the tracker marks the ticket blocked, then the ticket gets the `waiting` label (added by its numeric ID, resolved from the name) and a blocker comment through the Forgejo API. 4. Given a remote on `forgejo.swansoncloud.com` and no tracker key, when the tracker resolves, then it resolves to `forgejo:<owner>/<repo>` without `tea logins list`. 5. Given the needed token variable is not set, when a tracker call runs, then the error names that variable. 6. Given a fake client is injected, when an owner fetches, labels, or comments, then no network request is made. 7. Given the Gemfile change, when `bundle install` runs in `plugins/os-sdlc`, then `Gemfile.lock` lists `forgejo-api` and `octokit`, and `bundle exec ruby -e 'require "forgejo"'` succeeds. ## Owner sketch ```ruby class OsSdlc::IssueSource::Forgejo def initialize(slug, client: default_client) # default_client => ::Forgejo::Client.new(url:, api_key: ENV.fetch("FORGEJO_TOKEN")) def fetch(ticket_number) # repo.issues.get(n) => "title\n\nbody" end class OsSdlc::Tracker::Forgejo def initialize(slug, client: default_client) # repo = client.repo(owner, name) def add_waiting_label(id) # repo.issues.labels(id).add(labels: [label_id("waiting")]) def blocker_comment(id, message) # repo.comments.create(id, body: message) def fetch_body(id) # repo.issues.get(id).body private def label_id(name) # repo.labels.list.find { |l| l.name == name }.id end ``` ## Non-goals - os-backlog's tea and gh calls. They have their own tickets. ## Blocked by - #855 (closed; resolved by the `forgejo-api` gem, ADR-0257). ## Origin - Trigger: #845 audit; ADR-0256 decided 2026-09-30. - Improvised this session: none - Chain: ticket reads and blocker marks shell out to tea/gh, and Forgejo detection searches `tea logins list` text ← DESIGN: ADR-0123, amended by ADR-0256. - Root candidate: this ticket - Where: plugins/os-sdlc/lib/os_sdlc/adapter/issue_source/, plugins/os-sdlc/lib/os_sdlc/adapter/tracker/tea.rb - Session: f090dd39-8ad6-4f4e-8743-7bbedb8f08d4 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/f090dd39-8ad6-4f4e-8743-7bbedb8f08d4.jsonl
Author
Owner

Update 2026-09-30: #855 is resolved by the generated forgejo-api gem (ADR-0257, commit b68750db) in place of forgejo-client, so this ticket is no longer blocked. Source for plugins/os-sdlc/Gemfile: 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.issues.get(n) returns an Issue with .title/.body/.labels; repo.comments.create(n, body:); repo.issues.labels(n).add(labels: [id]) with the id resolved from repo.labels.list.

Update 2026-09-30: #855 is resolved by the generated `forgejo-api` gem (ADR-0257, commit b68750db) in place of `forgejo-client`, so this ticket is no longer blocked. Source for plugins/os-sdlc/Gemfile: `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.issues.get(n)` returns an Issue with `.title`/`.body`/`.labels`; `repo.comments.create(n, body:)`; `repo.issues.labels(n).add(labels: [id])` with the id resolved from `repo.labels.list`.
jared changed title from Replace tea and gh with forgejo-client and octokit in os-sdlc to Replace tea and gh with forgejo-api and octokit in os-sdlc 2026-09-30 20:26:20 +00:00
Author
Owner

Work started 2026-09-30 on branch ticket-858 (worktree .claude/worktrees/ticket-858), os-sdlc pipeline, target os-sdlc.

Work started 2026-09-30 on branch ticket-858 (worktree .claude/worktrees/ticket-858), os-sdlc pipeline, target os-sdlc.
Author
Owner

Pipeline note 2026-09-30: programmer dispatch 135 deadlocked ~14 min. It backgrounded its gate behind a stray cat > /dev/null (stdin never closed) and polled with a self-matching pgrep -f "os-sdlc-gate 135". Main loop killed the cat; the gate then ran. Skeptic verdict DROP for a follow-up ticket (one-off agent slip); re-file if it recurs.

Pipeline note 2026-09-30: programmer dispatch 135 deadlocked ~14 min. It backgrounded its gate behind a stray `cat > /dev/null` (stdin never closed) and polled with a self-matching `pgrep -f "os-sdlc-gate 135"`. Main loop killed the cat; the gate then ran. Skeptic verdict DROP for a follow-up ticket (one-off agent slip); re-file if it recurs.
Author
Owner

Resolution

Done: os-sdlc fetches tickets (IssueSource::Forgejo/Github) and labels blockers (Tracker::Forgejo) through forgejo-api and octokit in process; Tracker::Tea, TicketFetcher and tea-logins host detection deleted; Tracker::Backlog keeps the os-backlog body fetch for other trackers; Gemfile adds forgejo-api (git, ADR-0257), octokit, faraday-retry.

Evidence: main 67d32857 + 80c842c6 (History row) + 30a0094c (unused runner args and shell fakes removed). Suite 1337 runs, 4 failures, all pre-existing on main (#862). rubocop 0 offenses on changed files. Live read-only check against forgejo.swansoncloud.com: fetch(858) returned the ticket text, Tracker.for resolved Tracker::Forgejo, fetch_body(858) 5486 chars, fetch(999999) raised HarnessError HTTP 404. Implementation 17 escalated at programmer-repair (stale tea tests, masked by a Marshal crash in forked workers) and was hand-finished; test helper now preloads forgejo and octokit.

Follow-ups: #862 pre-existing GateEvaluatorProjectShellTest bundler failures (skeptic CREATE). Programmer gate deadlock: skeptic DROP, noted on this ticket. Leftover cleanup: skeptic DROP, done in 30a0094c. Each machine needs bundle install in plugins/os-sdlc (ADR-0257); none otherwise.

## Resolution **Done:** os-sdlc fetches tickets (IssueSource::Forgejo/Github) and labels blockers (Tracker::Forgejo) through forgejo-api and octokit in process; Tracker::Tea, TicketFetcher and tea-logins host detection deleted; Tracker::Backlog keeps the os-backlog body fetch for other trackers; Gemfile adds forgejo-api (git, ADR-0257), octokit, faraday-retry. **Evidence:** main 67d32857 + 80c842c6 (History row) + 30a0094c (unused runner args and shell fakes removed). Suite 1337 runs, 4 failures, all pre-existing on main (#862). rubocop 0 offenses on changed files. Live read-only check against forgejo.swansoncloud.com: fetch(858) returned the ticket text, Tracker.for resolved Tracker::Forgejo, fetch_body(858) 5486 chars, fetch(999999) raised HarnessError HTTP 404. Implementation 17 escalated at programmer-repair (stale tea tests, masked by a Marshal crash in forked workers) and was hand-finished; test helper now preloads forgejo and octokit. **Follow-ups:** #862 pre-existing GateEvaluatorProjectShellTest bundler failures (skeptic CREATE). Programmer gate deadlock: skeptic DROP, noted on this ticket. Leftover cleanup: skeptic DROP, done in 30a0094c. Each machine needs bundle install in plugins/os-sdlc (ADR-0257); none otherwise.
jared closed this issue 2026-09-30 21:04:11 +00:00
jared referenced this issue from a commit 2026-10-01 18:24:32 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jared/cc-os#858
No description provided.