Skip to content

docs(rfc): merge-request Layer 1 - command group RFC (DMD-1900) - #708

Merged
martinsifra merged 1 commit into
ms/dmd-1899/cli-layer-2from
ms/dmd-1900/cli-layer-1
Aug 27, 2026
Merged

docs(rfc): merge-request Layer 1 - command group RFC (DMD-1900)#708
martinsifra merged 1 commit into
ms/dmd-1899/cli-layer-2from
ms/dmd-1900/cli-layer-1

Conversation

@martinsifra

Copy link
Copy Markdown
Contributor

RFC for the kbagent merge-request command group (Layer 1), over MergeRequestService from
#703 (Layer 2, DMD-1899).

Docs only — no code. Stacked on ms/dmd-1899/cli-layer-2, so the diff is one file:
docs/merge-requests-layer1.md. Retarget to main once #703 merges.

What it decides

Layer 2 settles most of the surface — eleven service methods map to eleven commands with no
invention. This RFC decides what Layer 2 deliberately left to the caller:

Group merge-request + hidden mr alias, _DEV panel after branch
Files commands/merge_request.py + commands/_merge_request_render.py (split up front — 11 commands + 4 non-trivial renderers vs. the 800-line soft ceiling; output.py is already at 1013/1000)
Target --mr-id optional everywhere: --mr-idresolve_branch()find_merge_request_for_branch(). merge is not exempt
merge risk destructive--deny-destructive lets an agent run the whole flow and hands only the last step to a human
merge wait no --wait/--timeout in v1 (L3 always awaits, 600 s; a flag would mean re-reviewing L2+L3)
resolve --all no — rebase replaces; conflicts are meant to be walked, not waved away
serve full router ships with the commands, plus a serve-only by-branch route
branch merge deprecate-with-pointer, conditional — it works on projects without the feature, so it is not a 1:1 replacement

Facts the analysis surfaced

These shape the implementation and would otherwise be discovered at coding time:

  • The MR serializer emits no timestamps. No Created/Updated column is possible; the
    list arrives server-side createdAt DESC, so the renderer must preserve that order — it is
    the only chronological signal that survives.
  • FeatureNotEnabledError gets flattened by the house idiom. It is a ConfigError
    subclass carrying FEATURE_NOT_ENABLED; except ConfigError → error_code=CONFIG_ERROR
    (and the shared _handle_config_service_error) throws that away, leaving a --json
    consumer unable to tell "merge requests are not enabled" from a bad alias.
  • An empty --reviewer-id list clears the reviewer set. _optional_mr_fields sends
    reviewerIds whenever it is not None, so a Typer option yielding () silently wipes
    reviewers on any update that did not mention them. Must be normalised to None.
  • A scoped Storage token 403s on detail/conflicts/diff/resolve but list works
    the list action has no voter.
  • approve answers 422 in every state on a 0-approval project (its only from place,
    in_review, is unreachable because request_review is auto-finished). Neither it nor
    request-review has a happy path to assert. Same root cause means there is no close
    command
    : creator-request-changes is the UI's cancel but leaves state=development, so a
    close command would look like a no-op.

Open

E2E is not settled by this RFC and is marked as such. No project carries
branches-merge-requests, kbagent cannot provision one (ManageClient has no project create,
and Connection's suite makes its own), and the happy path necessarily merges into production.
The RFC records the proposed path — enable the feature on the existing E2E project via
kbagent feature project-add, gate with a FEATURE_NOT_ENABLED skip meanwhile — plus two
properties worth knowing: the merge writes to production (explicit teardown required) and
merge takes a project-wide lock, so concurrent runs collide.

🤖 Generated with Claude Code

Turns the working notes into an implementation-ready RFC for the
`kbagent merge-request` group over MergeRequestService (DMD-1899, #703).

Decided in this pass:

- `--mr-id` is optional everywhere, resolved `--mr-id` -> `resolve_branch()`
  -> `find_merge_request_for_branch()`; `merge` is not exempt.
- `merge` is classified `destructive`, so `--deny-destructive` lets an agent
  run the whole flow and hands only the last step to a human.
- No `--wait`/`--timeout` on merge in v1 (L3 always awaits, 600 s) and no
  `resolve --all` (rebase replaces; conflicts are meant to be walked).
- A full `server/routers/merge_requests.py` ships with the commands, plus a
  serve-only `by-branch` route; routers are not gated by CI, so a skip would
  reach users as an HTTP 404 with nothing red.

Facts the analysis surfaced that shape the commands:

- The MR serializer emits no timestamps, so no date column is possible and
  the renderer must preserve the server's `createdAt DESC` order.
- `FeatureNotEnabledError` carries `FEATURE_NOT_ENABLED`; the common
  `except ConfigError` idiom would flatten it to `CONFIG_ERROR`.
- An empty `--reviewer-id` list is sent as `reviewerIds: []` and clears the
  reviewer set -- it must be normalised to None.
- `detail`/`conflicts` 403 on a scoped token while `list` works.
- `approve` answers 422 in every state on a 0-approval project, and
  `request-review` lands directly in `approved` -- neither has a happy path
  to assert, and there is no `close` command for the same reason.

E2E is deliberately left open: no project carries the feature, kbagent
cannot provision one, and the happy path necessarily merges into
production. The RFC records the proposed path and marks it unsettled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@linear-code

linear-code Bot commented Aug 27, 2026

Copy link
Copy Markdown

DMD-1900

@martinsifra
martinsifra marked this pull request as ready for review August 27, 2026 01:27
@martinsifra
martinsifra marked this pull request as draft August 27, 2026 01:29
@martinsifra
martinsifra merged commit 85a3454 into ms/dmd-1899/cli-layer-2 Aug 27, 2026
@martinsifra
martinsifra deleted the ms/dmd-1900/cli-layer-1 branch August 27, 2026 01:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant