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:
| Field | Required | Meaning |
|---|---|---|
columnId | yes | UUID of the column the card starts in. |
priority | no | Overrides the spec's priority. |
assigneeId | no | UUID of a workspace member. Overrides the spec owner. |
dueDate | no | ISO 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:
409when the spec is still being generated, unless its generation has failed.409when the spec has no content yet.409when the spec already has a live card.400when the document is not a spec, or whenassigneeIdis not a member of the workspace.403when the spec belongs to another workspace.404when the spec does not exist, or whencolumnIdis 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.
How nima links a pull request to a ticket
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.
| Order | Strategy | What it matches |
|---|---|---|
| 1 | Branch | A 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. |
| 2 | Title | The key anywhere in the pull request title. |
| 3 | Body | The key anywhere in the pull request description. |
| 4 | AI match | nima 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 event | Card moves to |
|---|---|
| Opened | In Progress |
| Changes requested (a review) | In Progress |
| Reopened | In Progress |
| Ready for review | Testing |
| Merged | The 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| Key | Meaning |
|---|---|
version | Config version number. |
board | The 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. |
transitions | Maps a pull request event to a column title. Merged over the defaults. |
events | The 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.enabled | Turns the AI match strategy on or off. Default on. |
ai_matching.min_confidence | The 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:
| Verdict | Meaning |
|---|---|
aligned | The pull request does what the section says. |
drifted | The pull request does something different from the section. |
not_addressed | The 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.mergedfor each merged pull request andspec.shippedwhen 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,titleandstatusof the spec.card: the ticket (id,ticketkey,title,boardId,column,isDone), ornull.pullRequests: up to 20 linked pull requests, newest first. Each hasid,number,title,state(open,closedormerged),authorLogin,repoFullName,mergedAt,updatedAt, anddrift: that pull request's latest check, ornullwhen it was never checked.- Each
driftholdspullRequestId,status(pending,analyzing,completedorfailed),hasDrift,sections(per-section verdicts, empty until the check completes),matchStrategy(card_linkorfile_path),commitShaandcheckedAt. latestDriftis deprecated: it repeats only the newest per-pull-request verdict. ReadpullRequests[].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.
Anatomy of a spec
How nima writes a spec from a beacon: research, the decision pause, the required sections, bug-repair specs, and how to read or edit the result.
Knowledge graph
What nima's knowledge graph holds, where its facts come from, how contradictions and superseded knowledge are marked, and how to ask it.