feat: add issue create and comment (task 0004) #3

Merged
alexion merged 1 commits from task-0004-issue-create-and-comment into main 2026-07-11 20:46:22 -04:00
Owner

Implements .claude/tasks/0004-issue-create-and-comment.md.

What was built

The first mutations — issue create and issue comment — plus the shared machinery that pr create (task 0010) and the issue edit/close slices will reuse:

  • src/body-source.ts--body vs --body-file resolution (mutually exclusive).
  • src/lookup.ts — label and milestone name→ID resolution, case-insensitive.
  • parseFlags — a repeatable flag kind, accumulating into a separate lists map so --label can repeat without changing the type of the single-valued flags map.
  • fields.ts — the joined array-join extractor and selectExtraFields for --fields.

issue create emits issue: { number, title, state, url }; issue comment <n> echoes the created comment from the POST response with the body cleaned and truncated at 800 chars.

Deviations

  • Label lookup paginates. The spec says just GET /labels, but that endpoint pages (default 30), so a repo with more labels than one page would fail to resolve a perfectly valid name. It now pages at 50 until exhausted, with a 1000-label runaway guard.
  • issue comment also accepts --full (not in the spec's flag list). The shared truncation hint literally reads "use --full to see complete body"; without the flag, that hint names a command that errors out. Documented in issue comment --help.
  • No issue view suggestion after commenting on a PR. issue comment is deliberately permissive toward PR numbers, but issue view type-guards them — so the obvious next-step suggestion would have been a command guaranteed to fail. The PR case is detected from the response's pull_request_url (no extra call). pr view becomes the right suggestion once task 0009 lands.
  • --fields is additive, not a replacement: the spec calls these "extra fields available via --fields", so the defaults always render and --fields appends.

Verification

127 unit/integration tests pass; coverage 95.1% statements / 89.9% branches, above the configured thresholds.

Whether Gitea's milestone ?name= filter is itself case-insensitive is the one assumption fixtures cannot settle, so the end-to-end tier now seeds a mixed-case label and milestone and passes both in a different case. If live behaviour differs from the fixtures, CI fails rather than a user discovering it.

Review

Risk

Overall: MEDIUM

  • Blast radius: Medium — parseFlags and the fixture server sit under all commands and all existing tests.
  • Reversibility: Medium — rollback is trivial (purely additive, no schema), but these are the CLI's first write paths, and the new flag surface becomes public contract.
  • Test coverage: Low — ~550 lines of new fixture tests plus a live e2e tier.
  • Sensitive domain: Medium — token-authenticated mutations; --body-file reads a caller-supplied path (expected for a local CLI).
  • Size & complexity: Medium — ~700 lines over 8 files, but flat control flow.
  • Runtime criticality: Medium — dev tooling, yet these commands mutate real repositories.

Unaddressed findings

  • Standards (judgement call) — Divergent Change: src/commands/issue.ts is now ~540 lines holding four subcommands. Left as-is: tasks 0005–0007 add five more, and a split into src/commands/issue/<subcommand>.ts is better done as its own refactor than smuggled into a feature task. Flagged in the task's Implementation Notes.
  • Risk (minor): the label pagination cap (1000) would report a misleading "not found" for a label beyond it. Kept as a runaway guard; 1000 labels in one repo is not a realistic shape.
Implements [`.claude/tasks/0004-issue-create-and-comment.md`](.claude/tasks/0004-issue-create-and-comment.md). ## What was built The first mutations — `issue create` and `issue comment` — plus the shared machinery that `pr create` (task 0010) and the issue edit/close slices will reuse: - **`src/body-source.ts`** — `--body` vs `--body-file` resolution (mutually exclusive). - **`src/lookup.ts`** — label and milestone name→ID resolution, case-insensitive. - **`parseFlags`** — a `repeatable` flag kind, accumulating into a separate `lists` map so `--label` can repeat without changing the type of the single-valued `flags` map. - **`fields.ts`** — the `joined` array-join extractor and `selectExtraFields` for `--fields`. `issue create` emits `issue: { number, title, state, url }`; `issue comment <n>` echoes the created comment from the POST response with the body cleaned and truncated at 800 chars. ## Deviations - **Label lookup paginates.** The spec says just `GET /labels`, but that endpoint pages (default 30), so a repo with more labels than one page would fail to resolve a perfectly valid name. It now pages at 50 until exhausted, with a 1000-label runaway guard. - **`issue comment` also accepts `--full`** (not in the spec's flag list). The shared truncation hint literally reads "use `--full` to see complete body"; without the flag, that hint names a command that errors out. Documented in `issue comment --help`. - **No `issue view` suggestion after commenting on a PR.** `issue comment` is deliberately permissive toward PR numbers, but `issue view` type-guards them — so the obvious next-step suggestion would have been a command guaranteed to fail. The PR case is detected from the response's `pull_request_url` (no extra call). `pr view` becomes the right suggestion once task 0009 lands. - **`--fields` is additive**, not a replacement: the spec calls these "extra fields available via `--fields`", so the defaults always render and `--fields` appends. ## Verification 127 unit/integration tests pass; coverage 95.1% statements / 89.9% branches, above the configured thresholds. Whether Gitea's milestone `?name=` filter is itself case-insensitive is the one assumption fixtures cannot settle, so the end-to-end tier now seeds a mixed-case label and milestone and passes both in a *different* case. If live behaviour differs from the fixtures, CI fails rather than a user discovering it. ## Review ### Risk **Overall: MEDIUM** - Blast radius: Medium — `parseFlags` and the fixture server sit under all commands and all existing tests. - Reversibility: Medium — rollback is trivial (purely additive, no schema), but these are the CLI's first *write* paths, and the new flag surface becomes public contract. - Test coverage: Low — ~550 lines of new fixture tests plus a live e2e tier. - Sensitive domain: Medium — token-authenticated mutations; `--body-file` reads a caller-supplied path (expected for a local CLI). - Size & complexity: Medium — ~700 lines over 8 files, but flat control flow. - Runtime criticality: Medium — dev tooling, yet these commands mutate real repositories. ### Unaddressed findings - **Standards (judgement call) — Divergent Change:** `src/commands/issue.ts` is now ~540 lines holding four subcommands. Left as-is: tasks 0005–0007 add five more, and a split into `src/commands/issue/<subcommand>.ts` is better done as its own refactor than smuggled into a feature task. Flagged in the task's Implementation Notes. - **Risk (minor):** the label pagination cap (1000) would report a misleading "not found" for a label beyond it. Kept as a runaway guard; 1000 labels in one repo is not a realistic shape.
alexion added 1 commit 2026-07-11 20:39:25 -04:00
feat: add issue create and comment (task 0004)
All checks were successful
CI / test (pull_request) Successful in 24s
CI / test (push) Successful in 28s
f82414a933
Introduce the first mutations, along with the shared machinery the later
issue and PR mutation slices reuse.

- `issue create` with --title/--body/--body-file/--assignee/--label/
  --milestone/--fields, emitting `issue: { number, title, state, url }`
- `issue comment <n>`, echoing the created comment from the POST response
  with the body cleaned and truncated at 800 chars
- body-source resolution (--body vs --body-file), label and milestone
  name->ID lookup, repeatable flags, and the `joined`/`selectExtraFields`
  field extractors

Label lookup pages until exhausted, since a repo with more labels than one
page would otherwise fail to resolve a valid name. The end-to-end tier seeds
a mixed-case label and milestone and passes both in a different case, so the
case-insensitive lookup is verified against live Gitea rather than only
against fixtures.
alexion merged commit f82414a933 into main 2026-07-11 20:46:22 -04:00
alexion deleted branch task-0004-issue-create-and-comment 2026-07-11 20:46:22 -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#3