docs
Concepts

From spec to merged pull request

How a spec becomes a ticket, how nima links pull requests to it, checks drift on every push, and closes the loop when the last PR merges.

Delivery is the second half of the pipeline: Spec, PR, Closed. A spec is promoted to a ticket on a board, nima links the GitHub pull requests that name that ticket, compares every push against the spec, and when the last linked pull request merges it marks the spec shipped and tells the people who asked. For the whole pipeline see The loop.

On for every workspace. Tracking pull requests needs the GitHub integration connected, a board, and a repository linked to that board (all covered below). Promoting a spec to a ticket needs the documents:write scope, which only workspace owners and admins can hold. The spec routes are declared under the documents plan feature and the GitHub routes under github_ingestion; a workspace whose plan lacks one can get 403 INSUFFICIENT_PERMISSIONS with details.feature naming it.

Nothing on this page has a screen in the web app. The web app is an admin panel: sign in, onboarding, Integrations and Settings. You promote over the REST API, and you read delivery state over MCP, the REST API, Slack notices and GitHub. Links that nima sends to a spec land on Integrations.

Promoting a spec to a ticket

Promoting a spec to a ticket creates a card on a kanban board, links it to the spec, and moves the spec from draft or in_review to approved. It is the step that lets nima follow the spec into GitHub.

curl -X POST https://api.getnima.io/documents/$SPEC_ID/promote \
  -H "Authorization: Bearer $NIMA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"columnId": "<column uuid>", "priority": "high"}'

The route is POST /documents/:id/promote (no /v1 prefix) and needs the documents:write scope. The body takes:

FieldRequiredMeaning
columnIdyesUUID of the column the card starts in.
prioritynoOverrides the spec's priority.
assigneeIdnoUUID of a workspace member. Overrides the spec owner.
dueDatenoISO date string. Overrides the spec's due date.

A board must exist first. Boards, columns and cards are managed with session-only routes (/kanban/boards, /kanban/columns, /kanban/cards), so an API key cannot list them to find a columnId. nima creates no board automatically when a workspace is set up. A board created by the kanban route has four columns: TODO, In Progress, Testing and Done, with Done flagged as the done column.

A promote call fails with:

  • 409 when the spec is still being generated, unless its generation has failed.
  • 409 when the spec has no content yet.
  • 409 when the spec already has a live card.
  • 400 when the document is not a spec, or when assigneeId is not a member of the workspace.
  • 403 when the spec belongs to another workspace.
  • 404 when the spec does not exist, or when columnId is not a column in the workspace.

The status never moves backward: a spec already at approved or beyond keeps its status. When the spec has a repository, promoting also enrolls it in drift checks. The four spec statuses that nima writes are draft, approved, in_progress and shipped; the in_review value is legal but nothing sets it. See Anatomy of a spec.

Linking a repository to a board

nima processes pull request events only for a repository that is linked to a kanban board. Each repository links to one board, and each board to one repository.

Link one with PATCH /v1/github/repositories/:id/board and the body {"kanbanBoardId": "<board uuid>"}; send null to unlink. The call needs a signed-in owner or admin session (the MANAGE_INTEGRATIONS permission), not an API key. It returns 409 when another repository already uses that board. GET /v1/github/repositories lists repositories with their kanbanBoardId.

nima tries to link a repository and a board for you at two moments: when GitHub is installed, and each time a board is created. It links only if the workspace has exactly one unlinked repository and exactly one unlinked board at that moment. In every other case you link by hand.

A pull request event for a repository without a board is recorded and moves nothing. The same holds for a pull request that matches no card.

nima ties a pull request to a card by the card's ticket key, and takes the first strategy that matches. A ticket key is the workspace's ticket prefix, a dash and the card number, such as NW-42 for a workspace whose prefix is NW.

OrderStrategyWhat it matches
1BranchA key that follows a slash in the head branch name: feat/NW-42-retry-webhooks. A key at the start of the branch, with no slash before it, does not match.
2TitleThe key anywhere in the pull request title.
3BodyThe key anywhere in the pull request description.
4AI matchnima compares the title, description, branch and changed files to the cards on the linked board that are not archived, and links when its confidence is at least 0.7.

Only cards on the board linked to the repository, and not archived, are candidates. One pull request can link to several cards. The first time a pull request links to a card, the spec behind it moves to in_progress; it never moves backward from shipped.

Turn the AI match off, or change its threshold, in .nima.yml.

Card moves from pull request events

nima moves a linked card when the pull request changes state. The defaults, for a board with the standard columns:

Pull request eventCard moves to
OpenedIn Progress
Changes requested (a review)In Progress
ReopenedIn Progress
Ready for reviewTesting
MergedThe board's done column

These events link and enrich the card but move nothing: approved, synchronize (a new push), review requested, closed without merge, and draft.

A merged pull request moves the card to the column flagged as done, whatever its title. Other targets match a column by title, loosely. If your board uses different names, override the targets in .nima.yml.

Every pull request event that links to a card also updates the card, whether or not it moves. nima records the linked pull requests on it (number, title, URL, state, author, additions, deletions, changed files) and sets one label: pr:open while the pull request is open and pr:merged after it merges. A pull request closed without merge leaves no pr: label.

Cards move through the same path as a manual move, so a card synced from Jira also moves the linked Jira issue when Jira offers a matching transition. See GitHub for connecting the integration.

.nima.yml repository settings

A .nima.yml file at the root of a repository overrides how nima links and moves for that repository. Every key is optional.

version: 1
board: NW
transitions:
  opened: In Progress
  ready_for_review: Testing
  merged: Done
events: [opened, ready_for_review, changes_requested, reopened, merged]
ai_matching:
  enabled: true
  min_confidence: 0.7
KeyMeaning
versionConfig version number.
boardThe workspace's ticket prefix. Used only when the repository has no linked board; nima then uses a board of that workspace. Link the repository to a board instead when the workspace has more than one.
transitionsMaps a pull request event to a column title. Merged over the defaults.
eventsThe events nima acts on. Replaces the default list of ten (opened, ready_for_review, review_requested, synchronize, approved, changes_requested, reopened, merged, closed, draft).
ai_matching.enabledTurns the AI match strategy on or off. Default on.
ai_matching.min_confidenceThe AI match threshold. Default 0.7.

branch_pattern is accepted and stored, but no strategy reads it: the branch rule above is fixed.

nima reads the file from the default branch only when a push touches .nima.yml. It does not read it when GitHub is installed. Invalid YAML keeps the previous settings, and deleting the file clears them.

Drift checks on every push

nima compares the code in a pull request to the spec it delivers and reports where they diverge. A check runs when a pull request is opened, gets a new push, is reopened, or is merged.

Drift checks cover only specs that were promoted with a repository attached (the repository nima researched when writing the spec); a spec without one is never checked. nima finds the spec through the card link (pull request, card, board, enrolled spec). When there is no link, it falls back to any enrolled spec for that repository whose text mentions a segment of a changed file path. The result records which one it used: card_link or file_path.

Each spec section gets one verdict:

VerdictMeaning
alignedThe pull request does what the section says.
driftedThe pull request does something different from the section.
not_addressedThe pull request does not touch the section.

nima posts the result as a pull request comment headed "PRD Drift Check" followed by the spec title, with a per-section table, and updates that comment in place on later pushes. It posts only when the GitHub App is not suspended and has write access to pull requests. The comment's "View PRD in nima" link lands on Integrations, not on a spec page.

A drifted verdict on a pull request that is still open also notifies the spec owner's watch, in workspaces that have turned watches on; see Watches and reminders. Read drift with get_spec_delivery, described below.

When a spec ships

A spec ships when the last open pull request on its card merges. nima then sets the spec to shipped and closes the loop in four ways:

  • nima records a notification for the spec owner, Shipped: <spec title>. The admin panel does not show notifications today; the Slack reply and watch events below are how people hear about it.
  • nima replies once in each Slack thread that a signal behind the spec came from, with a reply starting Shipped: followed by the spec title and the pull request URL. It posts at most 20 replies per spec, and skips channels where nima is set to stay silent. A failed post does not stop the others.
  • Every signal behind the spec that has not reached a final state moves to resolved.
  • Watch events fire: pr.merged for each merged pull request and spec.shipped when the spec ships. They reach watchers only in workspaces that have turned watches on.

If another linked pull request is still open when one merges, the spec stays in_progress until the last one merges. nima does not write "shipped" back to Jira, and there is no per-person announcement setting.

Following delivery over MCP and the API

get_spec_delivery returns where one spec stands between ticket and merge. The REST route is GET /documents/:id/delivery and needs the documents:read scope. The input is the spec id, a UUID from list_specs. get_spec_delivery and list_specs are documented in Pipeline tools.

The response holds:

  • documentId, title and status of the spec.
  • card: the ticket (id, ticket key, title, boardId, column, isDone), or null.
  • pullRequests: up to 20 linked pull requests, newest first. Each has id, number, title, state (open, closed or merged), authorLogin, repoFullName, mergedAt, updatedAt, and drift: that pull request's latest check, or null when it was never checked.
  • Each drift holds pullRequestId, status (pending, analyzing, completed or failed), hasDrift, sections (per-section verdicts, empty until the check completes), matchStrategy (card_link or file_path), commitSha and checkedAt.
  • latestDrift is deprecated: it repeats only the newest per-pull-request verdict. Read pullRequests[].drift.

A spec that was never promoted returns card: null and an empty pullRequests list, with status 200. list_specs gives a lighter view: each spec's ticket, prCount and prState.

Handing the spec to an agent

A spec is written so a coding agent can run it. To have an agent work the ticket, give it the spec's markdown with get_document_markdown, or start an agent operation from it; see Agent operations. Have the agent put the ticket key in the branch name after a slash (feat/NW-42-...), so nima links the pull request without the AI fallback.

On this page