feat: add pr create and comment (task 0010) #4

Merged
alexion merged 1 commits from task-0010-pr-create-and-comment into main 2026-07-11 22:51:17 -04:00
Owner

Implements .claude/tasks/0010-pr-create-and-comment.md.

What was built

pr create takes --title (required), --body/--body-file, --base, --head, --assignee, --reviewer, repeatable name-resolved --label, and --milestone; --draft and --project are excluded, as the spec says.
An omitted --head defaults to the current local branch (git rev-parse --abbrev-ref HEAD), an omitted --base to the repository's default branch.
Before creating, an existing open PR for the same base/head pair short-circuits to pull_request: { number, url, already: true }; a successful create emits the action block created: { number, url }.

pr comment <n> posts through the shared issue-comment endpoint and returns the created comment as comment: { number, author, created, body }, truncated at 800 chars (ADR 0008).

The comment block is now built in one place (src/comment.ts) for both issue comment and pr comment — ADR 0008 requires the two to stay identical, and it existed twice after the first draft.

Deviations

  • A closed PR for the same branch pair does not short-circuit. Gitea's by-base-head lookup matches on the branches alone, so it can return a closed or merged PR; the spec's check is for an existing open PR, and a closed one's branches are free to be proposed again.
  • Names are resolved before the existence check. A misspelled --label must fail the same way whether or not the PR already exists; the other ordering saves one call but makes a typo fail on the first run and pass silently on the second.
  • pr comment also accepts --full — the shared truncation hint says "use --full to see complete body", which would otherwise name a command that errors out. Same deviation issue comment took in task 0004.
  • A 404 from pr comment is PR_NOT_FOUND, not ISSUE_NOT_FOUND. PR comments go through /issues/{index}/comments, so the spec's path-based rule would report a missing PR as a missing issue; the table's own header is "HTTP status | Context | Error code", and the command knows its target.
  • Next-step suggestions point at pr comment, not pr view, which does not exist until task 0009. Task 0009 should upgrade them.
  • Beyond the ask: an end-to-end suite. The criteria call only for fixture tests, but the idempotency check rests on how live Gitea's by-base-head lookup actually behaves; test/e2e/mutations.test.ts now seeds a branch and asserts both the 404-when-absent and found-when-present cases against a live instance.

Review

Overall: MEDIUM

  • Blast radius: Low — new pr.ts plus additive exports; the only existing-code edit is issue.ts moving onto the extracted number parser, with messages unchanged.
  • Reversibility: Medium — reverts as one commit, but ships a new CLI surface, and its writes (PRs, comments) persist on the remote.
  • Test coverage: Low — 26 new fixture tests plus the e2e suite; coverage 95.9% statements / 90.1% branches against thresholds of 92/87.
  • Sensitive domain: Low — no auth, permissions, concurrency, or migrations; the git subprocess uses fixed argv with no shell interpolation.
  • Size & complexity: Medium — ~330 source lines, linear control flow.
  • Runtime criticality: Medium — dev-facing CLI, but it performs irreversible mutations; duplicate creation is guarded by the idempotency check.

Unaddressed findings

  • Standards (judgement call) — duplicated payload assembly. The optional-field assignment in prCreate mirrors issueCreate's. It is a shape rather than logic, and collapsing it means a generic assignDefined helper; left for a third caller to justify.
  • Standards (judgement call) — duplicated test git sandbox. repoOnBranch/gitEnv in test/pr-create.test.ts overlap with detection.test.ts's private helpers, but deduping would drag that file's fake-tea sandbox machinery into harness.ts for no gain here.
  • Standards (judgement call) — withArticle in flags.ts. A vowel-regex article picker serving two nouns; kept because it lets call sites pass one plain noun and preserves the existing issue-side messages verbatim.
  • Spec (accepted) — findOpenPull treats every 404 as "no such PR", including a 404 from a nonexistent repository. The subsequent POST still classifies as REPO_NOT_FOUND, so the cost is one wasted request, not a wrong error code.
Implements `.claude/tasks/0010-pr-create-and-comment.md`. ## What was built `pr create` takes `--title` (required), `--body`/`--body-file`, `--base`, `--head`, `--assignee`, `--reviewer`, repeatable name-resolved `--label`, and `--milestone`; `--draft` and `--project` are excluded, as the spec says. An omitted `--head` defaults to the current local branch (`git rev-parse --abbrev-ref HEAD`), an omitted `--base` to the repository's default branch. Before creating, an existing open PR for the same base/head pair short-circuits to `pull_request: { number, url, already: true }`; a successful create emits the action block `created: { number, url }`. `pr comment <n>` posts through the shared issue-comment endpoint and returns the created comment as `comment: { number, author, created, body }`, truncated at 800 chars (ADR 0008). The comment block is now built in one place (`src/comment.ts`) for both `issue comment` and `pr comment` — ADR 0008 requires the two to stay identical, and it existed twice after the first draft. ## Deviations - **A closed PR for the same branch pair does not short-circuit.** Gitea's by-base-head lookup matches on the branches alone, so it can return a closed or merged PR; the spec's check is for an existing *open* PR, and a closed one's branches are free to be proposed again. - **Names are resolved before the existence check.** A misspelled `--label` must fail the same way whether or not the PR already exists; the other ordering saves one call but makes a typo fail on the first run and pass silently on the second. - **`pr comment` also accepts `--full`** — the shared truncation hint says "use `--full` to see complete body", which would otherwise name a command that errors out. Same deviation `issue comment` took in task 0004. - **A 404 from `pr comment` is `PR_NOT_FOUND`, not `ISSUE_NOT_FOUND`.** PR comments go through `/issues/{index}/comments`, so the spec's path-based rule would report a missing PR as a missing issue; the table's own header is "HTTP status | Context | Error code", and the command knows its target. - **Next-step suggestions point at `pr comment`, not `pr view`**, which does not exist until task 0009. Task 0009 should upgrade them. - **Beyond the ask: an end-to-end suite.** The criteria call only for fixture tests, but the idempotency check rests on how live Gitea's by-base-head lookup actually behaves; `test/e2e/mutations.test.ts` now seeds a branch and asserts both the 404-when-absent and found-when-present cases against a live instance. ## Review **Overall: MEDIUM** - Blast radius: Low — new `pr.ts` plus additive exports; the only existing-code edit is `issue.ts` moving onto the extracted number parser, with messages unchanged. - Reversibility: Medium — reverts as one commit, but ships a new CLI surface, and its writes (PRs, comments) persist on the remote. - Test coverage: Low — 26 new fixture tests plus the e2e suite; coverage 95.9% statements / 90.1% branches against thresholds of 92/87. - Sensitive domain: Low — no auth, permissions, concurrency, or migrations; the git subprocess uses fixed argv with no shell interpolation. - Size & complexity: Medium — ~330 source lines, linear control flow. - Runtime criticality: Medium — dev-facing CLI, but it performs irreversible mutations; duplicate creation is guarded by the idempotency check. ### Unaddressed findings - **Standards (judgement call) — duplicated payload assembly.** The optional-field assignment in `prCreate` mirrors `issueCreate`'s. It is a shape rather than logic, and collapsing it means a generic `assignDefined` helper; left for a third caller to justify. - **Standards (judgement call) — duplicated test git sandbox.** `repoOnBranch`/`gitEnv` in `test/pr-create.test.ts` overlap with `detection.test.ts`'s private helpers, but deduping would drag that file's fake-`tea` sandbox machinery into `harness.ts` for no gain here. - **Standards (judgement call) — `withArticle` in `flags.ts`.** A vowel-regex article picker serving two nouns; kept because it lets call sites pass one plain noun and preserves the existing issue-side messages verbatim. - **Spec (accepted) — `findOpenPull` treats every 404 as "no such PR"**, including a 404 from a nonexistent repository. The subsequent POST still classifies as `REPO_NOT_FOUND`, so the cost is one wasted request, not a wrong error code.
alexion added 1 commit 2026-07-11 21:04:09 -04:00
feat: add pr create and comment (task 0010)
All checks were successful
CI / test (pull_request) Successful in 30s
CI / test (push) Successful in 29s
590691fc96
`pr create` takes --title (required), --body/--body-file, --base, --head,
--assignee, --reviewer, repeatable name-resolved --label, and --milestone.
An omitted --head defaults to the current local branch; an omitted --base to
the repository's default branch. Before creating, an existing open PR for the
same base/head pair short-circuits to `pull_request: { number, url, already:
true }` rather than opening a duplicate; only an open PR does, since a closed
one's branches are free to be proposed again.

`pr comment <n>` posts through the shared issue-comment endpoint and returns
the created comment as `comment: { number, author, created, body }` (ADR 0008),
reporting a 404 as PR_NOT_FOUND since the caller asked about a pull request.

That comment block is now built in one place (src/comment.ts) for both issue
and pr comment, as ADR 0008 requires them to stay identical.
alexion merged commit 590691fc96 into main 2026-07-11 22:51:17 -04:00
alexion deleted branch task-0010-pr-create-and-comment 2026-07-11 22:51:17 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: alexion/gitea-axi#4