Skip to content

Commit 1bd7daf

Browse files
committed
docs(features): document jira ticket engraving in branches/commits/mrs
Adds comprehensive feature documentation including: - Implementation files and layer breakdown - Classification flow (first-turn classifier, persistence, artifact generation) - Trust boundary (threadId only, server re-derives context) - Resilience patterns (graceful degradation, hallucination guards) - Post-merge verification checklist item
1 parent 9d8bb8d commit 1bd7daf

1 file changed

Lines changed: 57 additions & 0 deletions

File tree

FEATURES.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Features implemented exclusively in MarCode that do **not** exist upstream. This
1111
- [Rich Tool Display Cards](#rich-tool-display-cards)
1212
- [Git Integration (Multi-Host)](#git-integration-multi-host)
1313
- [Jira Cloud Integration](#jira-cloud-integration)
14+
- [Jira Ticket Engraving in Branches / Commits / MRs](#jira-ticket-engraving-in-branches--commits--mrs)
1415
- [Theme System](#theme-system)
1516
- [Notifications](#notifications)
1617
- [Composer Enhancements](#composer-enhancements)
@@ -164,6 +165,61 @@ When merging upstream, **reject** any removal of:
164165

165166
---
166167

168+
## Jira Ticket Engraving in Branches / Commits / MRs
169+
170+
**Files:**
171+
172+
- `apps/server/src/git/Prompts.ts``buildClassifyImplementingJiraTicketsPrompt`, `COMMIT_JIRA_CONTEXT_RULE`, `PR_TITLE_REQUIRED_RULE`, `PR_BODY_RULE`, `BRANCH_RULE`, etc.
173+
- `apps/server/src/git/Services/TextGeneration.ts``classifyImplementingJiraTickets` method on `TextGenerationShape`; `jiraTickets?` on commit/PR/branch/title generation inputs
174+
- `apps/server/src/git/Layers/{Claude,Codex,Cursor,OpenCode}TextGeneration.ts``classifyImplementingJiraTickets` implementation per provider; `filterToAllowedKeys` hallucination guard
175+
- `apps/server/src/git/Layers/RoutingTextGeneration.ts` — fallback routing for the classifier alongside the existing `withFallback` pattern
176+
- `apps/server/src/git/Utils.ts``filterToAllowedKeys` utility
177+
- `apps/server/src/git/Layers/GitManager.ts``runStackedAction` resolves `jiraTickets` once via `JiraContextCollector` and threads them into `runFeatureBranchStep` / `runCommitStep` / `runPrStep`
178+
- `apps/server/src/jira/Services/JiraContextCollector.ts` + `Layers/JiraContextCollector.ts` — Effect service that walks `thread.messages`, extracts `<jira_context>` blocks, then **filters by `thread.implementingJiraTicketKeys`** so only classified tickets reach the auxiliary generators. Falls back to all mentions when classification hasn't run yet.
179+
- `apps/server/src/jira/threadJiraContext.ts``collectThreadJiraContexts` with token budgets + dedup
180+
- `apps/server/src/orchestration/Layers/ProviderCommandReactor.ts``maybeClassifyAndPersistImplementingJiraTickets` runs at first turn before branch-rename / title-gen, persists result via `thread.meta.update`
181+
- `packages/shared/src/jiraContext.ts` — shared `JiraTaskDraft`, `JiraTicketContext`, `extractTrailingJiraContexts`, `parseJiraContextEntry`, `jiraIssueKeysFromContexts` (re-exported by `apps/web/src/lib/jiraContext.ts`)
182+
- `packages/contracts/src/git.ts``threadId?: ThreadId` on `GitRunStackedActionInput`
183+
- `packages/contracts/src/orchestration.ts``implementingJiraTicketKeys: Schema.Array(JiraIssueKey)` on `OrchestrationThread` + `OrchestrationThreadShell`; same field optional on `ThreadMetaUpdateCommand` / `ThreadMetaUpdatedPayload`
184+
- `apps/server/src/orchestration/{decider,projector}.ts` + `Layers/{ProjectionPipeline,ProjectionSnapshotQuery}.ts` — projection plumbing for the new field
185+
- `apps/server/src/persistence/Services/ProjectionThreads.ts` + `Layers/ProjectionThreads.ts``implementingJiraTicketKeys` row column + JSON encoding
186+
- `apps/server/src/persistence/Migrations/031_ProjectionThreadsImplementingJiraTicketKeys.ts` + `Migrations.ts` — adds `implementing_jira_ticket_keys_json TEXT NOT NULL DEFAULT '[]'` to `projection_threads`
187+
- `apps/web/src/types.ts`, `apps/web/src/store.ts``Thread.implementingJiraTicketKeys`, `ThreadShell.implementingJiraTicketKeys` mapped through projection + meta-update event reducer
188+
- `apps/web/src/components/GitActionsJiraChips.tsx` — chip beside the git-actions group reads `thread.implementingJiraTicketKeys`; tooltip explains the classification semantics
189+
- `apps/web/src/components/GitActionsControl.tsx` — passes `activeServerThread?.implementingJiraTicketKeys` to the chip; passes `activeThreadRef.threadId` on `runStackedAction` mutation
190+
- `apps/web/src/lib/gitReactQuery.ts``gitRunStackedActionMutationOptions` accepts and forwards `threadId`
191+
192+
For enterprise users who live in Jira + GitLab/GitHub. The flow:
193+
194+
1. User `@jira:`-mentions tickets in any user message. Composer serializes them into a trailing `<jira_context>...</jira_context>` block per message (existing infra).
195+
2. **First-turn classifier** runs on the server via the configured text-generation model. Input: the user's message text + every mentioned ticket. Output: the subset of ticket keys the user is **actively implementing** (vs. mentioned for context / reference / pattern-matching). Even a single mentioned ticket goes through the classifier — a lone reference ticket (e.g. "fix this the same way we fixed `@jira:OTHER-99`") must not leak into the branch/commit/PR.
196+
3. Result is persisted as `thread.implementingJiraTicketKeys` via the existing `thread.meta-updated` event path. Survives reconnects, server restarts, and app reopens.
197+
4. **Branch:** the worktree-rename produces `marcode/PROJECT-111-<short-name>` (or `marcode/PROJECT-111-PROJECT-222-<short-name>` for multi-ticket implementations). Commit-time feature-branch flow wraps to `feature/PROJECT-111-…`.
198+
5. **Commit messages:** Jira tickets are passed to the prompt as **CONTEXT ONLY**. The model uses ticket descriptions to inform the body's "why" but is explicitly forbidden from including the ticket key in the subject or body — no `Refs:` trailer, no `[KEY]` prefix, no parenthesized suffix. Commit messages read naturally on their own.
199+
6. **PR/MR titles:** the implemented ticket key is **mandatory and visible**. The model picks placement (bracketed prefix, parenthesized suffix, or interpolated scope) — non-negotiable that the key appears.
200+
7. **PR/MR bodies:** Jira description informs `## Summary`'s "why". No `## Tickets` sidecar section, no `Refs:` trailer — the title already carries the key.
201+
8. **UI chip** (`GitActionsJiraChips`) sits beside the Commit / Push / MR action button and shows the classified implementing keys (primary + `+N` for additional tickets, hover popover lists all). Reads from `thread.implementingJiraTicketKeys` directly — server is the source of truth, web is read-only feedback.
202+
203+
### Trust boundary (Critical)
204+
205+
The contract change is **only `threadId?: ThreadId`** on `GitRunStackedActionInput`. The client never ships ticket descriptions back to the server. The server re-derives Jira context from stored message text via `JiraContextCollector` and applies the classified-keys filter. Avoids prompt-injection vector and parser drift.
206+
207+
When merging upstream, **reject** any change that:
208+
209+
- Adds a `jiraTickets[]` payload field to `GitRunStackedActionInput`.
210+
- Drops the `threadId` field or stops the web from passing it.
211+
- Inlines Jira context parsing into web — keep it in `@marcode/shared/jiraContext` so server + web stay aligned.
212+
- Removes the `withDecodingDefault([])` on `OrchestrationThread.implementingJiraTicketKeys` / `OrchestrationThreadShell.implementingJiraTicketKeys` (breaks decoding of pre-feature DB rows).
213+
- Drops migration `031_ProjectionThreadsImplementingJiraTicketKeys` from `Migrations.ts`.
214+
215+
### Resilience
216+
217+
`JiraContextCollector` wraps the read pipeline in `Effect.catchCause` — any failure (read-model miss, parse error, OAuth-expired Jira upstream) degrades silently to `[]`. The classifier itself is wrapped the same way and falls back to `mentionedTickets` (no filtering) when classification fails. Commit/PR actions are never blocked by the Jira layer.
218+
219+
The classifier prompt defaults to **excluding** ambiguous tickets — better to omit a key from artifacts than engrave a reference ticket the user never intended to implement. The model output is filtered through `filterToAllowedKeys` so the model can't invent a key.
220+
221+
---
222+
167223
## Theme System
168224

169225
**Commit:** `4e52e0e9` (and `fc042b23`)
@@ -316,6 +372,7 @@ After any upstream merge, verify each feature still works:
316372
- [ ] All rich tool display cards
317373
- [ ] GitLab MR support + dynamic PR/MR labels
318374
- [ ] Jira OAuth + board selection + task chips
375+
- [ ] Jira ticket engraving — first-turn classifier persists `implementingJiraTicketKeys`; branch/PR title carry the key; commit messages stay clean (no `Refs:` trailer, no `[KEY]` prefix); chip beside git-actions reads `thread.implementingJiraTicketKeys`; migration `031` adds the column; `@marcode/shared/jiraContext` hosts the shared parser
319376
- [ ] All 24+ themes
320377
- [ ] Turn notifications with sound
321378
- [ ] Directory picker popover

0 commit comments

Comments
 (0)