[Bugfix #1137] Fix gitea forge preset against the real tea CLI - #1146
[Bugfix #1137] Fix gitea forge preset against the real tea CLI#1146pseudoseed wants to merge 5 commits into
Conversation
The gitea preset invoked `tea <entity> list/view/whoami/comment`, whose
flattened `--fields` output (or missing flags/subcommands) doesn't match the
Gitea REST shape that forge-contracts.ts and the jq normalizers assume. Route
the read concepts through `tea api`, the raw REST passthrough that returns
exactly that shape:
- user-identity: `tea api user | jq .login` (`tea whoami` has no --output json)
- pr-view: `tea api repos/<repo>/pulls/N` → PrViewResult
- pr-list: `tea api repos/<repo>/pulls?state=open` → PrListItem[]
(now also populates real reviewRequests/isDraft/body)
- pr-exists: `tea api repos/<repo>/pulls?state=all` with nested .head.ref/.merged
- issue-view: `tea api repos/<repo>/issues/N` + a second call for the comments
ARRAY (Gitea's issue object reports `comments` as an int count,
which would crash consumers' `.comments.filter(...)`)
- recently-merged: `tea api repos/<repo>/pulls?state=closed`, filter .merged,
using the real .merged_at
- issue-comment: `tea comments add` (`tea issues` has no `comment` subcommand)
`tea api` needs an explicit owner/repo path segment (unlike `tea <entity>`,
which auto-detects it from the local git remote), and most concepts are invoked
without CODEV_REPO set, so each api-based script derives owner/repo from the
origin remote, honoring CODEV_REPO when present.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stubs a fake `tea` on PATH answering `api <endpoint>` with captured Gitea REST fixtures (tea isn't in CI, per cluesmith#920), points the scripts at a throwaway repo with a gitea remote, runs each real script, and asserts the normalized output conforms to forge-contracts.ts — incl. comments-as-array, merged-only filtering, open/merged/closed pr-exists cases, and CODEV_REPO override. Also updates the cluesmith#568 pr-exists assertion for gitea to match the new `state=all` query param (was `--state all` flag). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
waleedkadous
left a comment
There was a problem hiding this comment.
Excellent work — thank you for the disciplined re-do, and apologies for the review latency. This is what a model bugfix PR looks like: every tea-CLI deficiency documented in-script with reasoning, the REST passthrough returning exactly the shape forge-contracts.ts expects, and a genuinely well-built regression suite (fake tea on PATH serving captured REST fixtures, the real scripts executed, contract-shape assertions, the comments-as-int crash and null-login team reviewers both covered). We verified every output mapping field-by-field against the contracts — all conform — and the switch from string-interpolated jq to --arg in pr-exists is a quiet security improvement worth crediting.
One substantive question before merge, and two optional polish items:
1. Pagination cap (the one we'd like addressed or answered). Gitea servers cap page size at max_response_items (default 50), so ?limit=200 likely returns 50 items with no client-side pagination in the raw passthrough. That means pr-exists?state=all can false-negative for a branch whose PR isn't in the most recent ~50 (which would block a porch pr_exists gate), and recently-merged (previously --limit 1000) can miss on a busy repo. A pagination loop (page=1..N until a short page) would settle it — or at minimum a comment documenting the server-side cap and the false-negative window, so the next debugger isn't blind. Happy with either; we'd just like the behavior to be chosen rather than inherited.
2. (Polish, optional) With no origin remote or an unusual URL, REPO silently becomes empty/garbage and tea api "repos//…" fails with a confusing 404. An explicit [ -n "$REPO" ] || { echo "…set CODEV_REPO" >&2; exit 1; } naming the remedy would fit this repo's fail-fast convention — ideally factored once since the derivation appears in five scripts.
3. (Polish, optional) A failed comments fetch silently yields comments: [] — indistinguishable from "no comments" for consumers reading issue discussion. A stderr warning on the degraded path would keep the graceful behavior while leaving a trace.
Verdict: approve once item 1 is addressed (fix or documented caveat — your choice). Items 2–3 are welcome in this PR or a follow-up, contributor's choice.
…ast, warn on degraded comments Addresses PR cluesmith#1146 review feedback: 1. Pagination (blocking). Gitea caps list responses at max_response_items (default 50), so the raw `&limit=200` passthrough silently truncated — pr-exists could false-negative a PR beyond the first ~50 (blocking a porch pr_exists gate) and recently-merged could miss on a busy repo. New shared helper `_lib.sh#tea_api_paged` walks page=1..N at limit=50, concatenates the arrays, and stops on a short/empty page with a hard 100-page ceiling. Chosen behavior: paginates, ceiling 100 pages. Wired into pr-exists, pr-list, recently-merged; output shape unchanged (same jq normalizers). 2. REPO derivation, fail-fast + factored. The CODEV_REPO/origin-derivation was duplicated in five scripts. Factored into `_lib.sh#gitea_repo`, sourced by issue-view, pr-exists, pr-list, pr-view, recently-merged. It now validates the result is a clean owner/repo and, if not, prints a stderr message naming CODEV_REPO as the remedy and exits non-zero (was a confusing `repos//…` 404). POSIX sh, $0-relative source; not a forge concept (KNOWN_CONCEPTS allowlist). 3. Degraded comments warn. issue-view still degrades a failed comments fetch to [], but now writes a stderr warning so it's distinguishable from a genuinely uncommented issue. stdout stays pure JSON (parsed by forge.ts). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks for the thorough review — all three items addressed in 1. Pagination — fixed (real page loop, not a comment). You're right that 2. REPO fail-fast, factored once — done. The 3. Degraded comments warn — done. Full suite green in the worktree: 3449 passed | 48 skipped, 0 failures ( |
|
@waleedkadous let me know if there's anything else that needs to be addressed with this one :) |
Verified against a real Forgejo +
|
| concept | result |
|---|---|
user-identity |
FAIL Incorrect Usage: flag provided but not defined: -output |
issue-view |
FAIL jq: Cannot index array with string "html_url" |
pr-list |
FAIL, exit 0 — prints Error: invalid field 'description' and still returns success |
recently-merged |
FAIL, exit 0 — same |
pr-view |
returns a list, not the requested PR |
issue-list, issue-search, pr-exists, recently-closed, auth-status |
OK |
The two exit-0 cases are the nastiest: a caller checking the exit status sees success and gets an error string where JSON should be.
After — this branch, same environment
| concept | result |
|---|---|
user-identity |
OK — user |
issue-view |
OK — object with title, body, state, url, comments[] |
pr-list |
OK — normalized PrListItem[] |
pr-view |
OK — single PR object, correct one |
pr-exists |
OK — true |
recently-merged |
OK — merged-only, correct merged_at ordering |
All six previously-broken concepts now work. No regressions in the five that already worked.
Two notes
The comments-as-int catch is real and would have bitten immediately. Gitea returns comments as an integer count on the issue object; our issue-view on the released version failed exactly there. The second call for the comments array is necessary, not defensive.
One nearly-false report from me, worth stating so nobody repeats it. My first run of this branch's issue-view failed with Cannot index array with string "title". That was my harness, not your code — I had exported CODEV_ISSUE_NUMBER where the contract is CODEV_ISSUE_ID, so the path resolved to the issue list endpoint. With the correct variable it works. Flagging it because the failure mode is plausible-looking and someone else testing this could draw the wrong conclusion.
Unrelated gap this surfaced
pr-create is not a forge concept at all, so gh pr create stays hardcoded in the skeleton prompts (porch/prompts/pr.md, protocols/{air,spir,pir,bugfix,maintain}/…). That means a Gitea/Forgejo user still needs a gh shim on PATH no matter how complete this preset becomes. Not this PR's problem — filing separately — but relevant if anyone assumes a working gitea preset makes gh unnecessary.
Happy to re-run against any further revisions.
Fixes #1137.
Bugfix-protocol re-do of the earlier SPIR-style PR #1138 (now closed), per maintainer request. Same root cause, plus a real regression test.
Problem
The
giteaforge preset was authored against the Gitea REST API JSON shape, but the scripts invoke theteaCLI, whose output shape differs — and several concepts referenced flags/fields/subcommandsteadoesn't have. Per the in-repo#920note,teawasn't available in the authoring environment, so the preset was never run end-to-end.Fix
Route the read concepts through
tea api(raw REST passthrough returning the shapeforge-contracts.ts+ the jq normalizers expect):tea api user | jq .login(tea whoamihas no--output json)tea api repos/<repo>/pulls/N→PrViewResult(incl. additions/deletions)tea api repos/<repo>/pulls?state=open→PrListItem[]tea api repos/<repo>/pulls?state=allwith nested.head.ref/.mergedtea api repos/<repo>/issues/N+ a second call for the comments array (Gitea reportscommentsas an int count, which would crash.comments.filter(...))tea api repos/<repo>/pulls?state=closed, filter.merged, using real.merged_attea comments add(tea issueshas nocommentsubcommand)tea apineeds an explicit owner/repo path segment, so each api-based script derives owner/repo from the origin remote (honoringCODEV_REPOwhen set).Testing
teaon PATH answeringapi <endpoint>with captured Gitea REST fixtures (tea isn't in CI, per vscode: editor-tab webview for rich backlog search #920), runs each real script, and asserts the normalized output conforms toforge-contracts.ts— incl. comments-as-array, merged-only filtering, open/merged/closedpr-exists, andCODEV_REPOoverride.pr-existsassertion for the newstate=allquery param.🤖 Generated with Claude Code