Adopt our two stranded upstream fixes: gitea tea-api reads (#1146) + pr-create forge concept (#1458) - #5
Merged
Merged
Conversation
…rge concept
`pr-create` was the one forge operation with no concept behind it. It was
absent from KNOWN_CONCEPTS, no provider shipped a script for it, and every
protocol prompt wrote `gh pr create` literally — so a project with
`forge.provider: gitea` fully configured still shelled out to `gh` at the
single most important write in the protocol, and only worked if someone kept
a `gh`→forge shim on PATH.
Contract (env in, JSON out — the shape every other concept uses, so it stays
callable from executeForgeCommand):
in: CODEV_PR_TITLE, CODEV_PR_BODY, and optional CODEV_PR_BASE / _HEAD /
_REPO / _DRAFT
out: {"number": <int>, "url": "<web url>"}
- scripts/forge/github/pr-create.sh — `gh pr create` with the flags it already
took, so nothing changes for GitHub users.
- scripts/forge/gitea/pr-create.sh — `tea pulls create --description` (not
`--body`), tea's rendered output pushed to stderr, and the new PR looked up
via `tea pulls list --output json` instead of parsing that rendered view.
- scripts/forge/gitlab/pr-create.sh — `glab mr create`, marked UNVERIFIED
(`glab` is not installed here); without it the gitlab preset falls through
to `gh`, which is this bug.
- porch substitutes `{{pr_create_command}}` into phase prompts, mirroring the
existing `pr-merge` injection, and falls back to "open the PR manually" when
the concept is disabled.
Verified end to end against a live Forgejo with tea 0.14.2: the created PR's
body, read back from the server, is byte-identical to the input (428 bytes,
quotes/backticks/$VAR/backslash/fenced block all intact), and base, head and
title match. On 0.14.2 with a single login and an explicit --head, `tea pulls
create` needs no --repo/--login and does not prompt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Porch substitutes every occurrence of the token, so the sentence explaining the token rendered as "Porch substitutes /abs/path/pr-create.sh with your forge's pr-create concept command". Found by rendering the real BUGFIX pr prompt through the locally built porch. The prose now refers to "the command above", and the regression test pins exactly one occurrence per prompt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…QUEST_CHANGES) Three real defects, each with a test that fails without the fix. 1. Inline overrides received empty inputs (codex). The prompts set the inputs as an assignment prefix — `CODEV_PR_TITLE=… <cmd>`. A script reading the environment works, but an inline override, which is the documented form (`"pr-merge": "glab mr merge \"$CODEV_PR_NUMBER\" --yes"`), has its `"$CODEV_PR_TITLE"` argument expanded by the calling shell BEFORE the assignment applies, so it got "". The prompts now export. Pinned by a test that renders the shipped prompt, extracts the bash block and executes it against an inline override. 2. `codev doctor` reported `pr-create … set not found` (claude). extractExecutable returns a script's first substantive command, which is `set -e` here, so doctor looked for `set` on PATH and could no longer tell a Gitea user that `tea` is missing — for the one write that matters. Added a `# forge-executable: <tool>` declaration honoured ahead of the heuristic, and a shell-builtin skip list. Verified against the built dist: github→gh, gitea→tea, gitlab→glab. 3. `gitea/pr-create.sh` looked the new PR up without `--limit`, unlike every sibling gitea script. On a repo with more open PRs than tea's default page, it would create the PR and then exit 1 saying it could not find it. Now `--limit 200`, matching the house convention. Also: the disabled-concept fallback rendered as a `#` comment — valid shell that exits 0 without opening a PR. It now writes to stderr and returns false. gemini APPROVE. Full suite 4888 passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ing an empty one CMAP round 2 (codex, REQUEST_CHANGES): the scripts validated CODEV_PR_TITLE but not CODEV_PR_BODY. `--body ""` / `--description ""` succeeds everywhere, so a caller who forgot the variable entirely got a bodyless PR at exit 0 — exactly the silent failure cluesmith#1455's testing notes describe. The scripts now distinguish unset from deliberately empty (`${CODEV_PR_BODY+x}`) and fail before reaching the forge CLI, with a test per provider covering both cases. Also documents CODEV_PR_LOGIN (read by the gitea script, previously named nowhere) in the script header, the contract and forge.md — claude's minor note on the same round. gemini APPROVE, claude APPROVE. Full suite 4891 passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t concept counts CMAP round 3 non-blocking notes (all three lanes APPROVE): - `gitea/pr-create.sh` read `.head` as a plain string (true on tea 0.14.2) while sibling `pr-exists.sh` reads `.head.ref`. Accept either shape — a lookup miss here reports failure for a PR that was actually created, which invites a duplicate on retry. - `forge.ts` and `arch.md` still said "15 concepts" and omitted `pr-create` (along with `issue-search` and `repo-archive`). Now 18, listed. Re-verified live against Forgejo after the change (PR #17: body, base and head correct read back from the server; closed and branch deleted). Full suite 4891. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
…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>
…rop the lookup The gitea `pr-create` ran `tea pulls create` (whose output is a rendered, ANSI-decorated view, not parseable) and then searched for the PR it had just made with `tea pulls list --limit 200`. That search was built on a disproven assumption. cluesmith#1146 established, and this change re-confirmed against live Forgejo 15.0.2, that Gitea caps every list response at the server's `max_response_items` — default 50. `settings/api` reports 50, and a `?limit=200` request returns exactly 50 items where paging at 50 returns 53. So `--limit 200` silently truncates: on a busy repo the just-created PR falls off the first page, and pr-create reported created the PR but could not find an open pull for head '<branch>' and exited 1 for a PR that exists — inviting a duplicate retry at the single most important write in the protocol. Rather than paginate the lookup, remove it. `tea api -X POST repos/{owner}/{repo}/pulls` RETURNS the created PR — `number` and `html_url` — in its response body, so there is nothing to search, nothing to race, and nothing to truncate. It also drops the `<user>:<branch>` head-matching heuristic: the API resolves an owner-qualified head itself. Live verification against tea 0.14.2 + Forgejo 15.0.2 turned up three defects in the obvious version of that change. Each is the same bug class as cluesmith#1455 itself — an operation accepted and then silently not performed — so each is handled in code, not left as a caveat. 1. `tea api` EXITS 0 on HTTP errors, printing the error body. Since the whole change replaces a lookup with a single call, trusting that exit code would reintroduce cluesmith#1455's silent success inside the fix for it: a 404 or 422 would be reported as a created PR. The response is therefore asserted to BE a PR object — an object carrying a numeric `number` AND a non-empty browser URL — and anything else fails loudly with the response body. Pinned by tests that feed an error object, an array, a string-typed `number`, a numberless object, `null` and an empty body, all at exit 0. The one case where `number` is present but the URL is not gets its own message: the PR WAS created, so it names the number and says not to retry. Reading that as "nothing happened" is how duplicates get opened. 2. `base` is REQUIRED by the API — it answers `[Base]: Required` — where `tea pulls create` defaulted it client-side. Silently posting against the wrong base would be worse than erroring, so an unset CODEV_PR_BASE now resolves the repo's default branch explicitly, and fails with a clear message if that cannot be resolved. 3. `draft: true` in the payload is SILENTLY IGNORED (the response comes back `draft: false`), so CODEV_PR_DRAFT=1 would have been an accepted-and-ignored flag. Gitea marks a draft by a `WIP:` title prefix — exactly what `tea pulls create --draft` does — so that is now implemented, and verified server-side to produce `draft: true`. Also verified live: `{owner}`/`{repo}` are substituted by tea from the repo context, with `--repo owner/name` supplying it when the cwd has no Gitea remote (checked with https and scp-style remotes, and from a GitHub-remote cwd); `url` on the create response is the browser page, so `.html_url // .url` lands the right one in the contract; and the body round-trips byte-identically, being built with `jq --arg` and fed on stdin (`-d @-`) rather than surviving an argv round-trip. The unresolvable-repo case used to surface as a bare `404 page not found`; it now names CODEV_PR_REPO as the remedy, matching the fail-fast ergonomics of `_lib.sh#gitea_repo` in cluesmith#1146 without taking a dependency on that PR — this change stands alone and the two can merge in either order. Tests: the gitea half of the concept suite is rewritten against a `tea api` stub. Every new case fails against the previous script and passes against this one, including an explicit assertion that no `pulls`/`list`/`--limit` call is made at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…luesmith#1458 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…luesmith#1458 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…n PR cluesmith#1458 1. linear preset now explicitly disables pr-create instead of silently falling through to the github default (`gh pr create`) — the same silent-fallthrough bug class cluesmith#1455 closes, just found by the integration reviewer in a different preset. 2. Add `.` and `source` to extractExecutable's SHELL_BUILTINS, so a script that opens with `. "$(dirname "$0")/_lib.sh"` (the shape sibling PR cluesmith#1146's read scripts use) isn't misreported by `codev doctor` as needing an executable literally named `.`. 3. gitea/pr-create.sh's default-branch resolution named CODEV_PR_BASE as the remedy even when the real failure was an unresolvable repo (GET 404) — the POST path already named CODEV_PR_REPO correctly for the same root cause; the GET path now matches it. Reviewer: amrmelsayed (CMAP integration review, 2026-08-17). Verdict: APPROVE with these three pre-merge recommendations. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adopts two fixes that have been sitting unmerged on
cluesmith/codevinto this fork, so we stop waiting on upstream.teaCLI — routes gitea reads throughtea api, paginates, fails fast on REPO, warns on degraded commentspr-createforge concept so non-GitHub forges can open PRs (gitea/github/gitlab scripts + contract + porch prompt wiring)Both authored by @pseudoseed.
Why this is safe
(
pr-exists,pr-list,pr-view,recently-merged,user-identity); [Bugfix #1455] Add a pr-create forge concept so non-GitHub forges can open PRs cluesmith/codev#1458 adds pr-create(
pr-create.shper forge,forge-contracts.ts,forge.ts, porch prompts).builder/task-24AO-1137is not contained inbuilder/bugfix-1455— six commits are uniqueto it, so both had to land. Merged 1137 first as the older foundation, then 1455.
@cluesmith/codevsuite green on this branch: 269 files, 5352 passed, 48 skipped, 0 failed.Merge, don't squash
Per this repo's convention, merge with
--merge. The individual commits document the work andmatter here for a second reason: when upstream eventually merges cluesmith#1146/cluesmith#1458, our next
upstream/mainsync will see the same changes and resolve as a no-op rather than a conflict.Note on CI
mainis currently red ontower-api.e2e.test.ts(13 failures, all401where a400/404wasexpected). That is inherited from the upstream commit we fast-forwarded to (
ff855d3e7) and isunrelated to this PR — upstream's own CI is green on that identical SHA, and the file passes
locally here. Tracked separately.