Files
gitea-axi/.claude/tasks/0007-issue-blocks-blocked-by.md
alexion 6c15e6082f
All checks were successful
CI / test (pull_request) Successful in 35s
CI / test (push) Successful in 35s
feat: add issue blocks and blocked-by (task 0007)
2026-07-13 19:41:04 -04:00

3.0 KiB

spec, blocked-by
spec blocked-by
gitea-axi 0001-scaffold-and-issue-list-core

What to build

The two Gitea-specific dependency subcommand groups — issue blocks <list|add|remove> and issue blocked-by <list|add|remove> — over Gitea's blocks and dependencies endpoints. blocks manages downstream dependents (issues that cannot proceed until this one is resolved); blocked-by manages upstream blockers. List output blocks are blocked_issues and blocking_issues; add outputs blocks: { issue, blocks } / blocked_by: { issue, blocked_by }. Idempotency: add of an existing relationship does a fetch-first check and returns already: true; remove of a nonexistent relationship is silent success; genuine validation failures (self-reference, cycles) surface as VALIDATION_ERROR via the 422 mapping. No gh-axi equivalent exists — the interface shape follows this spec alone.

Acceptance criteria

  • issue blocks list <n> and issue blocked-by list <n> render their respective output blocks with count lines and explicit empty states
  • issue blocks add <n> <target> outputs blocks: { issue: n, blocks: target }; issue blocked-by add <n> <blocker> outputs blocked_by: { issue: n, blocked_by: blocker }
  • Adding an existing relationship returns already: true (fetch-first check, no duplicate POST); removing a nonexistent relationship exits 0 silently-successfully
  • Self-reference and cycle rejections from Gitea surface as VALIDATION_ERROR (exit 2) with the server's message
  • Fixture-server tests cover list, add, idempotent re-add, remove, idempotent re-remove, and a 422 cycle rejection for both groups

Implementation Notes

Both groups are one config-parameterised implementation (DependencyGroup): blocks over /issues/{index}/blocks, blocked-by over /issues/{index}/dependencies, differing only in endpoint calls and the spec-fixed output names (blocked_issues/blocking_issues, blocks/blocked_by).

Decisions made mid-implementation, where the spec was silent:

  • remove output. The spec fixes the add output shape but not remove's. A successful deletion reports <noun>: { issue, <target>, removed: true }; a no-op removal (relationship already absent) reports already: true instead, mirroring add's no-op and honouring the action/entity-block convention (a no-op reports the already-reached state rather than claiming an action it did not perform). Both add and remove are fetch-first against the fully paginated current set, so a nonexistent issue surfaces as ISSUE_NOT_FOUND before any mutation.
  • list row fields. Rendered as number, title, state — the identifying essentials; the spec did not fix a row shape.

Review follow-ups addressed in this change:

  • Extracted parseIssueNumber into flags.ts so the two-positional dependency parser and the existing parsePositionalNumber share one positive-integer rule and message (was duplicated).
  • Added test coverage for the ISSUE_NOT_FOUND path (issue itself absent), which the code comments claim but nothing exercised.