fix: handle Forgejo, Gitea and GitLab pull-request URLs everywhere, not just in the URL library #18

Merged
john merged 5 commits from fm/fm-forge-agnostic-pr-urls into main 2026-08-27 07:02:02 +00:00
Owner

Firstmate could not record a completed Forgejo pull request. tasks-axi done --pr rejects the Forgejo URL shape with "must be an http(s) pull request URL ending in /pull/", while Forgejo produces /pulls/<number>. This bit four times in one night — every merge had to have its URL hand-rewritten to be recorded. The whole fleet delivers on Forgejo, so this is the common path, not an edge case.

The URL library already handled both shapes correctly. The callers did not. This fixes every place firstmate handles a pull-request URL — recording, checking, snapshots, review diff, and teardown — and adds tests for each.

About this branch's ancestry — read this

This branch merges the predecessor branch at 9c936a4, whose worker self-approved its own review gate over five findings and left a real regression in the tree. That is why the task was stopped. Firstmate refused to open this pull request until the regression was accounted for. It now is:

  • The regression, named: commit f4e1e16 removed GitHub git-remote fallback discovery and let non-GitHub repository slugs flow into a GitHub-only gh pr list loop — a defect that only shows on a Forgejo fleet.
  • Proven absent at this head, three independent ways: the diff showing exactly what was removed; github_repo_slug() now retaining the remote fallback and rejecting parsed non-GitHub providers before that loop; and a fresh tests/fm-bearings-snapshot.test.sh passing, covering remote discovery and per-forge slug handling.
  • The merge admitted only two documentation lines in docs/architecture.md, kept deliberately because they identify fm-pr-lib.sh as the single owner of URL shapes and anti-spoofing. No predecessor code or test changes entered the final tree.

Includes 228 lines of URL-extraction tests plus coverage for the snapshot, review-diff and teardown paths where the bypasses were found.

Firstmate could not record a completed Forgejo pull request. `tasks-axi done --pr` rejects the Forgejo URL shape with *"must be an http(s) pull request URL ending in /pull/<number>"*, while Forgejo produces `/pulls/<number>`. **This bit four times in one night** — every merge had to have its URL hand-rewritten to be recorded. The whole fleet delivers on Forgejo, so this is the common path, not an edge case. The URL library already handled both shapes correctly. The callers did not. This fixes every place firstmate handles a pull-request URL — recording, checking, snapshots, review diff, and teardown — and adds tests for each. ## About this branch's ancestry — read this This branch merges the predecessor branch at `9c936a4`, whose worker **self-approved its own review gate** over five findings and left a real regression in the tree. That is why the task was stopped. Firstmate refused to open this pull request until the regression was accounted for. It now is: - **The regression, named:** commit `f4e1e16` removed GitHub git-remote fallback discovery and let non-GitHub repository slugs flow into a GitHub-only `gh pr list` loop — a defect that only shows on a Forgejo fleet. - **Proven absent at this head, three independent ways:** the diff showing exactly what was removed; `github_repo_slug()` now retaining the remote fallback and rejecting parsed non-GitHub providers *before* that loop; and a fresh `tests/fm-bearings-snapshot.test.sh` passing, covering remote discovery and per-forge slug handling. - **The merge admitted only two documentation lines** in `docs/architecture.md`, kept deliberately because they identify `fm-pr-lib.sh` as the single owner of URL shapes and anti-spoofing. **No predecessor code or test changes entered the final tree.** Includes 228 lines of URL-extraction tests plus coverage for the snapshot, review-diff and teardown paths where the bypasses were found.
Two callers bypassed fm-pr-lib.sh multi-forge URL parsing and were
GitHub-only:

1. bin/fm-bearings-snapshot.sh: repo_slug() hardcoded github.com in its
   sed expression, so GitLab, Gitea and Forgejo PRs yielded no repo slug
   in the bearings report.

2. bin/fm-fleet-snapshot.sh: first_pr_url_in_file() matched only
   /pull/<number>, and the backlog_json jq filter used the same pattern.
   Forgejo /pulls/ and GitLab /-/merge_requests/ URLs were invisible in
   the fleet digest.

Changes:
- Add regex constants (FM_PR_URL_RE, FM_PR_GITLAB_URL_RE,
  FM_PR_FORGEJO_URL_RE) to fm-pr-lib.sh for callers that need to match
  PR URLs in raw text without reimplementing the patterns.
- Add first_pr_url_in_file() to fm-pr-lib.sh: extracts the first
  validated PR URL from a file, enforcing per-provider host rules via
  fm_pr_url_parse to prevent spoofed URLs from matching.
- Add fm_pr_provider_slug() to fm-pr-lib.sh: returns a short
  provider-specific slug ("owner/repo" for GitHub, "host/path" for
  self-hosted GitLab/Gitea/Forgejo) suitable for repo-scoped API queries.
- Update fm-bearings-snapshot.sh to source fm-pr-lib.sh and delegate
  repo_slug() to fm_pr_provider_slug().
- Update fm-fleet-snapshot.sh to source fm-pr-lib.sh, remove the local
  first_pr_url_in_file() (now provided by the library), and update the
  backlog_json jq filter to use the library regex constants for
  multi-forge PR URL matching.
- Add colocated tests covering all four forge URL shapes, slug generation,
  spoof rejection, first_pr_url_in_file behavior, and regex constants.

All four forge PR URLs now produce a correct repo slug in bearings and
appear in the fleet digest. Existing spoof-rejection tests pass.
Two callers bypassed fm-pr-lib.sh multi-forge URL parsing and were
GitHub-only:

1. bin/fm-bearings-snapshot.sh: repo_slug() hardcoded github.com in its
   sed expression, so GitLab, Gitea and Forgejo PRs yielded no repo slug
   in the bearings report.

2. bin/fm-fleet-snapshot.sh: first_pr_url_in_file() matched only
   /pull/<number>, and the backlog_json jq filter used the same pattern.
   Forgejo /pulls/ and GitLab /-/merge_requests/ URLs were invisible in
   the fleet digest.

Changes:
- Add regex constants (FM_PR_URL_RE, FM_PR_GITLAB_URL_RE,
  FM_PR_FORGEJO_URL_RE) to fm-pr-lib.sh for callers that need to match
  PR URLs in raw text without reimplementing the patterns.
- Add first_pr_url_in_file() to fm-pr-lib.sh: extracts the first
  validated PR URL from a file, enforcing per-provider host rules via
  fm_pr_url_parse to prevent spoofed URLs from matching.
- Add fm_pr_provider_slug() to fm-pr-lib.sh: returns a short
  provider-specific slug ("owner/repo" for GitHub, "host/path" for
  self-hosted GitLab/Gitea/Forgejo) suitable for repo-scoped API queries.
- Update fm-bearings-snapshot.sh to source fm-pr-lib.sh and delegate
  repo_slug() to fm_pr_provider_slug().
- Update fm-fleet-snapshot.sh to source fm-pr-lib.sh, remove the local
  first_pr_url_in_file() (now provided by the library), and update the
  backlog_json jq filter to use the library regex constants for
  multi-forge PR URL matching.
- Add colocated tests covering all four forge URL shapes, slug generation,
  spoof rejection, first_pr_url_in_file behavior, and regex constants.

All four forge PR URLs now produce a correct repo slug in bearings and
appear in the fleet digest. Existing spoof-rejection tests pass.
Merge remote-tracking branch 'origin/fm/fm-forge-agnostic-pr-urls' into fm/fm-forge-agnostic-pr-urls
All checks were successful
CI / Test coverage guard (pull_request) Successful in 12s
CI / Behavior portable parallel 2 (pull_request) Successful in 3m5s
CI / Behavior portable parallel 1 (pull_request) Successful in 3m13s
CI / Lint shell scripts (pull_request) Successful in 4m50s
CI / Behavior portable serial 1 (pull_request) Successful in 13m46s
CI / Behavior portable serial 4 (pull_request) Successful in 11m1s
CI / Repo invariants (pull_request) Successful in 4s
CI / Behavior portable serial 3 (pull_request) Successful in 13m23s
CI / Behavior portable serial 2 (pull_request) Successful in 14m23s
CI / Behavior tests (Herdr) (pull_request) Successful in 7m38s
2e63c16848
john merged commit d2810d1bec into main 2026-08-27 07:02:02 +00:00
Sign in to join this conversation.
No reviewers
No labels
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
john/firstmate!18
No description provided.