feat(jira): Jira Cloud integration -- parity with Linear (#288)#302
feat(jira): Jira Cloud integration -- parity with Linear (#288)#302mayakost wants to merge 20 commits into
Conversation
Phase 1 of Jira Cloud integration (aws-samples#288). Extends the ChannelSource discriminant on both sides of the wire and updates the agent-side comment so the runtime knows 'jira' is a recognized channel value; no behavior changes yet.
Phase 2 of Jira Cloud integration (aws-samples#288). Mirrors the Linear constructs file-for-file. Composite PKs use cloudId as the tenant prefix (`{cloudId}#{projectKey}`, `{cloudId}#{accountId}`) so the same project key or account id stays unambiguous across distinct Atlassian tenants. Tables are unwired until Phase 4 — JiraIntegration instantiates and grants them.
Phase 3 of Jira Cloud integration (aws-samples#288). Mirrors Linear's adapter shape: per-tenant OAuth resolver (auth.atlassian.com), X-Hub-Signature HMAC verify with per-tenant + stack-wide fallback, REST-based feedback poster (ADF-wrapped, no reaction primitive — marker folded into text), and three Lambdas (webhook, processor, link). Non-trivial bit: the processor diffs `changelog.items[]` where `field === 'labels'` and tokenizes the space-separated `fromString` / `toString` to detect a label add — Atlassian's diff format differs from Linear's `updatedFrom.labelIds`. Includes a minimal ADF→markdown walker for issue descriptions. Handlers reference JIRA_* env vars set by the JiraIntegration construct in Phase 4; they don't deploy yet.
Phase 4 of Jira Cloud integration (aws-samples#288). Mirrors LinearIntegration: 3 DDB tables, dedup table (8h TTL), 3 Lambdas (webhook/processor/link), API routes under /jira/*, per-tenant `bgagent-jira-oauth-*` IAM grants, cdk-nag suppressions. Stack wiring grants the agent runtime GetSecretValue on the per-tenant prefix and pipes the workspace registry table + Get/Put grant into the orchestrator (matches Linear's path for pre-container failure feedback). Synth confirms clean CloudFormation + no nag findings.
Phase 5 of Jira Cloud integration (aws-samples#288). Refactors channel_mcp.py from a single-channel gate to a CHANNEL_MCP_BUILDERS dispatch dict so adding future channels stays one-entry. Adds resolve_jira_oauth_token() to config.py mirroring the Linear resolver — same race-handling, same fail-closed semantics; only differences are the endpoint (auth.atlassian.com, JSON body) and the env-var name (JIRA_API_TOKEN). Pipeline now dispatches to the right resolver based on channel_source. JIRA_MCP_URL is flagged in-source as needs-verification — Atlassian's Remote MCP may still be preview-gated; if so, fall back to a REST shim in a future jira_reactions.py module (Plan B). Tests: 6 new Jira test cases in test_channel_mcp.py; full agent suite remains green (825 passed).
Phase 6 of Jira Cloud integration (aws-samples#288). Minimal v1 surface (4 of 10 Linear subcommands), per scoping decision. Mirrors the Linear CLI shape where the contracts are similar: - jira-oauth.ts ports linear-oauth.ts. Atlassian's token endpoint takes JSON (Linear takes form-encoded). offline_access scope is required for a refresh_token. fetchAccessibleResources() resolves cloudId + siteUrl post-consent. - commands/jira.ts: app-template prints dev-console values; setup drives the OAuth dance + writes the per-tenant secret + registry row + webhook signing secret; link does dry-run preview UX; map writes the project → repo row. Deferred to follow-ups: add-workspace, update-webhook-secret, invite-user (with self-link picker), list-projects.
Covers signature verify pass/fail, dedup, event filtering, label-add detection (create vs update changelog), and Cognito-authenticated linking. 56 tests, mirrors the Linear handler test surface.
- docs/guides/JIRA_SETUP_GUIDE.md — OAuth 3LO app, scopes, webhook registration, label trigger, project mapping, troubleshooting - docs/decisions/ADR-014-jira-integration.md — Jira Cloud only, OAuth 3LO, label trigger, MCP outbound; documents the Jira-vs-Linear divergences - README, USER_GUIDE, ROADMAP — add Jira to channel listings - sync-starlight.mjs + astro.config.mjs — register the Jira guide mirror; regenerate Starlight content under docs/src/content/docs/ Completes the docs phase of aws-samples#288.
Bring `mise run build` green on the jira integration branch: - check-types-sync: allowlist JiraLinkResponse as CLI-only, matching SlackLinkResponse/LinearLinkResponse (link responses are inlined server-side; no CDK source-of-truth type) - channel_mcp.py: move Callable into a TYPE_CHECKING block (ruff TC003; safe under `from __future__ import annotations`) - agent.test.ts: bump expected DynamoDB table count 13 -> 17 for the four new Jira tables (project/user/workspace-registry/webhook-dedup) - test_config.py: cover resolve_jira_oauth_token (cache, fallback, refresh, concurrent-refresh, malformed/expiry paths); agent coverage 70.41% -> 72.91% - jira-oauth-resolver.test.ts: new suite (32 tests) mirroring the Linear resolver tests; clears the CDK statement/line/function/branch gates - jira.ts / jira-oauth.ts: ESLint --fix cosmetic edits (quote-props, redundant template literals) Tests: 294 CLI + 837 agent + 1896 CDK, all passing.
Jira webhooks created via the Settings → System → Webhooks UI do not include a top-level `cloudId` in their payload (only app/OAuth-registered dynamic webhooks do). Without it the processor can't resolve the tenant, so it dropped the event and never created a task — the inbound trigger silently failed for the common single-tenant, UI-webhook setup. Add a safe fallback: when `payload.cloudId` is absent, scan the workspace registry and use the sole `active` tenant. Deliberately refuses to guess when zero or multiple active tenants exist (returns undefined → event dropped), so the multi-tenant design is preserved — a multi-tenant operator must use a webhook that carries its own cloudId. `grantReadData` on the registry table already covers the Scan, so no IAM change is needed. Adds tests for: sole-tenant recovery (task created), empty registry (drop), and multiple active tenants (ambiguous → drop).
Jira-origin tasks now comment on the originating issue at start
("🤖 picked up…") and on completion ("✅ finished — PR: <url>" / "❌ …"),
matching the Linear integration's progress UX.
Why a REST shim instead of the Atlassian Remote MCP: the hosted MCP
(mcp.atlassian.com) requires an interactive, browser-based OAuth 2.1 flow
with dynamic client registration — it does NOT accept the stored Jira REST
OAuth token as a Bearer header, so it fails to connect from a headless
agent ("claude mcp list" → Failed to connect; no mcp__jira-server__* tools
load). The Jira REST API accepts the same stored token (it carries
write:jira-work), so comments go via POST /rest/api/3/issue/{key}/comment
on the cross-region api.atlassian.com/ex/jira/{cloudId} base.
- New `jira_reactions.py`: gated by channel_source=='jira' + required
metadata; swallows all network/auth errors (comments are advisory, never
gate the pipeline); auth circuit-breaker mirrors linear_reactions.
- Wired into pipeline.py at task start, normal finish (with PR url), and
the crash path — parallel to the existing Linear reaction hooks.
- prompt_builder: Jira tasks now get NO MCP-comment addendum (the earlier
Linear-only gate already skipped them); instructing the agent to use the
non-loading MCP tools would just waste turns. Comments are out-of-band.
Adds test_jira_reactions.py (gate, ADF body, success/failure/PR variants,
error-swallowing, auth circuit breaker) and channel-addendum tests.
The 'Merge branch main' commit (c84cc66) left an invalid import block: a missing comma after PR_WORKFLOW_IDS (syntax error) plus a stale PR_TASK_TYPES import that main's aws-samples#248 removed from config.py. ruff rejected the file, aborting the agentcore build. Drop the orphaned PR_TASK_TYPES import and fix the comma.
krokoko
left a comment
There was a problem hiding this comment.
Thanks for this PR — it's an impressively thorough port of the Linear template. The OAuth refresh/rotation handling in jira-oauth-resolver.ts (including the Atlassian rotated-refresh-token race) and the four-way verifyJiraRequestForTenant outcome union are genuinely excellent, and the divergences from the Linear copy (changelog diffing, cloudId tenancy, ADF flattening) are all well-justified. The review below is detailed because the PR is large and security-relevant, not because the foundation is weak.
Verdict: Request changes — CI is currently red, the docs mirror will fail the mutation gate, there's a coupled multi-tenant signature-binding gap, and the ADR/docs still describe the pre-pivot MCP outbound design.
Blocking issues
-
CI is red —
agent/tests/test_config.py:11is missing a comma afterPR_WORKFLOW_IDS. This is aSyntaxErrorat pytest collection time, so the entire agent suite (including the newtest_jira_reactions.pycircuit-breaker tests and the 12-caseTestResolveJiraOauthTokensuite) is currently not running. Once fixed, those suites look well-designed. -
Stale Starlight mirrors — the "Fail build on mutation" gate will reject. Running
node docs/scripts/sync-starlight.mjsat PR HEAD dirtiesdocs/src/content/docs/using/Overview.mdandgetting-started/Quick-start.md(the latter inherited via themainmerge, but it needs a re-sync here). Please runmise //docs:syncand commit the regenerated mirrors. -
Multi-tenant signature binding (coupled pair):
cli/src/commands/jira.ts:539-547mirrors the stack-wide signing secret into every per-tenant OAuth bundle, so one shared secret effectively verifies for all tenants.- A payload verified via the stack-wide fallback carries no binding between the verified secret and
payload.cloudId— the processor then trusts the body-suppliedcloudIdto select the tenant, project→repo mapping, and OAuth bundle (cdk/src/handlers/jira-webhook.ts:136-147,jira-webhook-processor.ts:208,234). A holder of the stack-wide secret can steer a webhook at any tenant's mappings. - Related fail-open:
jira-webhook.ts:152skips the replay-window check entirely whentimestampis absent (Linear rejects a missing timestamp), and timestamp-less deliveries collapse to a single…#unknowndedup key.
Suggested fix: don't mirror the stack-wide secret into per-tenant bundles; after a stack-wide-fallback verification, refuse any
cloudIdother than the sole-tenant fallback result; and log (or reject) when the replay check is skipped so the bypass is observable. Given the project's fail-closed tenet, I'd treat this as blocking. -
ADR-014 and several docs/docstrings still describe the abandoned MCP-outbound design. The final commit correctly pivoted outbound to REST (
jira_reactions.py) — andagent/src/prompt_builder.py:135-146documents the reality perfectly — but the following still claim MCP outbound:docs/decisions/ADR-014-jira-integration.md(still says "Nojira_reactions.pyREST module", statusproposed),JIRA_SETUP_GUIDE.md:31-40,163-168,USER_GUIDE.md:14, the newROADMAP.mdentry,agent/src/channel_mcp.py:55-96,agent/src/config.py:333-344,jira-webhook-processor.ts:172, andjira-integration.ts:70-76. Additionally,channel_mcp.pystill writes a Jira MCP entry that the PR's own docstrings say cannot connect from a headless agent (and emits the SSE URL with"type": "http") — please either drop the"jira"entry fromCHANNEL_MCP_BUILDERSor gate it with an in-band log explaining it's expected to fail. The ADR should also be updated for the actual dedup key ({issueKey}#{webhookEvent}#{timestamp}— the ADR says timestamp-only) and, since #296 already merged an ADR-014, renumbered to ADR-015. -
User-facing instructions reference a non-existent CLI command. The unmapped-project feedback comment (
jira-webhook-processor.ts:247) tells admins to runbgagent jira onboard-project …— the real command ismapand requires a<cloud-id>positional. The CLI's own "Next steps" hint (cli/src/commands/jira.ts:555) also omits the required<cloud-id>. Both printed commands fail verbatim. -
Dead orchestrator wiring with surplus IAM.
cdk/src/stacks/agent.ts:878-898grants the orchestrator registry read +GetSecretValue/PutSecretValueonbgagent-jira-oauth-*"so the concurrency-cap rejection path can post a Jira comment" — butorchestrate-task.tsonly implementsnotifyLinearOnConcurrencyCap; there is no Jira equivalent. Please either implementnotifyJiraOnConcurrencyCap(parity) or remove the grant until the feature lands — unused write-capable IAM on every tenant's credentials is worth avoiding.
Test coverage gaps (would love to see these here, given the surfaces they guard)
- No CLI Jira tests (~993 lines across
cli/src/jira-oauth.ts+commands/jira.ts) — Linear has bothlinear-oauth.test.tsand command tests. The token exchange/refresh-rotation and the missing-refresh_tokenbranch are the highest-consequence untested code in the PR. - No multi-tenant signature test — Linear has a 9-case
linear-webhook-multi-workspace.test.tscovering exactly themismatch/revoked/no-downgrade distinctions that matter for blocking issue 3. jira-feedback.tsis mocked in every test that touches it — the ADF body shape (which Jira REST v3 will 400 on if wrong), timeout, and non-2xx paths have zero direct coverage. Linear shipslinear-feedback.test.ts.- No construct test for
jira-integration.ts— the only construct-level coverage is the table-count bump inagent.test.ts; Linear's construct test asserts key schemas, the dedup TTL attribute, and env wiring. - Suggest parameterizing
cdk/test/contracts/stored-oauth-token-parity.test.tsto also cover the JiraStoredOauthToken↔StoredJiraOauthTokenpair — currently that cross-language contract is prose-only.
Non-blocking suggestions
jira-link.ts:96-112: the Put (active mapping) + Delete (pending row) aren't atomic — considerTransactWriteItems, or return 200 with a WARN if the delete fails after a successful Put (today the user is told linking failed when it actually succeeded).USER_GUIDE.md:7: "There are five ways to interact" — the list now has six.- Consider keeping
client_secretout of the per-tenant bundle the agent role can read — a prompt-injected agent could exfiltrateclient_secret+refresh_tokenand mint tokens outside AWS. Splitting client credentials into a separate secret would harden this. resolveSoleTenantCloudId(jira-webhook-processor.ts:89-109): theScanCommandis unguarded and the processor has no top-level catch/DLQ (parity with Linear, so platform-wide rather than this PR's regression) — a try/catch + a metric on fallback usage would make silent drops countable.- Tiny key-builder helpers for the composite keys (
{cloudId}#{projectKey},{cloudId}#{accountId},pending#{code}) would single-source formats currently interpolated independently at write and read sites (jira-webhook-processor.ts:234,585,jira-link.ts:69,99,111) — the existingjiraOauthSecretName()is the precedent. jira-webhook-processor.ts:123: the| stringin thewebhookEventunion collapses the literal narrowing; and thecloudIddoc comment atjira-webhook.ts:61sits abovematchedWebhookIdsrather than the field it describes.- The 24h replay freshness window is generous; ~1h matches Atlassian's actual retry behavior.
What's notably good
jira-oauth-resolver.test.ts(37 cases) is a model suite —invalid_grantwith rotated vs. same refresh token, concurrent-refresh recovery that skips the second POST, non-fatalPutSecretValuepersistence failure.- The strict (throwing) registry/secret lookups on the verification path, so a transient infra error can't silently downgrade a per-tenant-secured tenant to the stack-wide fallback — exactly the right fail-closed choice.
- Dedup rollback on processor-invoke failure (
jira-webhook.ts:200-227), including the don't-mask-the-original-error nested case. - The advisory comment swallowing in
jira_reactions.pyis logged, circuit-broken, and faithfully mirrorslinear_reactions.py. prompt_builder.py:135-146is the single source of truth on the REST pivot — every other doc just needs to be brought in line with it.
Happy to re-review quickly once the CI fix + mirror sync land; the security binding (issue 3) and the ADR reconciliation (issue 4) are the two I'd most like to see addressed before merge.
test_prompts.py:_config built base from a homogeneous str literal, so ty inferred dict[str, str] and rejected the spread into TaskConfig's bool/int/list fields (17 invalid-argument-type errors). Annotate base as dict[str, Any], matching the existing helper in test_runner.py. This failure was previously masked by the lint syntax error that aborted the build before typecheck ran.
Code Review — Jira Cloud IntegrationReviewed the security-critical paths (webhook HMAC verification, OAuth resolution/refresh, task creation, agent runtime). Overall this is solid, production-minded work that faithfully mirrors the Linear adapter with well-documented divergences. Below are the findings. Significant issues1. Agent-side token refresh can permanently break a tenant (Atlassian rotating refresh tokens)
This differs from Linear, whose copied trust model assumes the stored refresh token survives. It only triggers if the agent catches a token within 60s of expiry before any Lambda refreshes it, but when it hits it silently breaks the tenant. Consider having the agent never refresh — fail closed and let the orchestrator/Lambda path (which has 2. Image-attachment extraction is likely dead code for real Jira issues Minor issues3. ADF→markdown conversion runs twice per issue. 4. Multi-tenant security of the stack-wide fallback secret. When a 5. 6. 7. HMAC over Done well
Test coverageStrong — OAuth refresh state machine (concurrent-refresh race, invalid_grant, malformed expiry, network failure), circuit breaker, channel gating, dry-run-must-not-write, and case-sensitive link codes are all covered. Suggested additions: a test asserting the agent-side refresh's non-persistence behavior (issue # 1), and a processor test against a realistic ADF media node (would surface the dead-code behavior in # 2). RecommendationI'd treat # 1 (agent refresh burning the rotating refresh token) as a blocker pending confirmation of Atlassian's token semantics, and # 2 as should-fix-or-remove. The rest are fine as follow-ups. 🤖 Generated with Claude Code |
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #302 +/- ##
=======================================
Coverage ? 86.81%
=======================================
Files ? 186
Lines ? 43540
Branches ? 4288
=======================================
Hits ? 37798
Misses ? 5742
Partials ? 0 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Three more issues that were masked behind the earlier lint/typecheck failures, all surfaced once the build progressed to its 'fail on mutation' gate: - ruff format reflowed two long boolean/string lines in jira_reactions.py and test_jira_reactions.py that were committed unformatted. - USER_GUIDE.md still referenced the retired `pr_review` task_type on the intro 'For example' line (a aws-samples#248 merge leftover); the rest of the guide uses `coding/pr-review-v1`. Fixed the source and regenerated the Starlight mirror (using/Overview.md). - Quick-start mirror was missing the Node.js prerequisite line present in the QUICK_START.md source; docs-sync adds it. Full `mise run build` now completes with no working-tree mutation.
…n refresh, ADR/docs
Blocking (krokoko):
- Multi-tenant signature binding: webhook receiver flags stack-wide
verification to the processor, which then ignores the body cloudId and
binds to the sole active tenant (drops when ambiguous). CLI no longer
mirrors the stack-wide secret into new per-tenant bundles; stack-wide is
seeded once from the first tenant. Missing-timestamp replay skip is logged.
- Renumber ADR-014 -> ADR-015 (collides with workflow-driven-tasks); rewrite
to the implemented REST-outbound reality (status accepted), correct dedup
key, binding + refresh-ownership sections. Reconcile JIRA_SETUP_GUIDE,
USER_GUIDE ("six ways"), ROADMAP, channel_mcp.py (placeholder + in-band
log), jira-webhook-processor.ts, jira-integration.ts, agent.ts.
- Fix non-existent CLI command in feedback: onboard-project -> map
<cloud-id> <project-key> --repo (processor + CLI next-steps hint).
- Implement notifyJiraOnConcurrencyCap (Linear parity) so the orchestrator
IAM grant is used and Jira users aren't silently dropped on the cap.
Significant (ayushtr):
- Agent never refreshes the Jira token (Atlassian rotates refresh_tokens;
agent has GetSecretValue only). Use the Lambda-written token verbatim and
fail closed when expiring; Lambda path owns all refreshes.
- ADF media nodes (external images) now render to markdown so attachment
extraction works; ADF->markdown computed once and reused.
Minor: one-sided clock-skew-tolerant timestamp freshness, base64-body guard,
replay window 24h->1h, resolveSoleTenantCloudId Scan comment.
Tests: agent no-refresh/fail-closed suite, multi-tenant binding tests,
base64/missing-timestamp/stack-wide-flag webhook tests, ADF media-node test,
notifyJiraOnConcurrencyCap parity suite. Docs mirrors regenerated.
Relates to aws-samples#288
|
Thanks both — this was an excellent, security-focused review. Pushed @krokoko (blocking)
@ayushtr-aws (significant)
Minor: timestamp freshness is now one-sided with a clock-skew allowance (#6); a base64-encoded body is rejected before verification (#7); Deferred (happy to do in a follow-up)The broader Linear-parity test suites — full CLI Verification: agent 1007 tests green; CDK Jira handler suites + synth suites pass under the pinned Node (22); |
The 'Merge branch main' commit (0f47343) dropped the closing ');' on the Jira mirrorMarkdownFile() call where it interleaved with main's new 'Deploy preview screenshots' mirror block, producing a SyntaxError that broke the //docs:sync build step (and thus the whole build job).
The 'Merge branch main' (0f47343) dropped the closing '});' on the JiraWorkspaceRegistryTableName CfnOutput where main's new GitHubScreenshotIntegration block was spliced in, cascading into ~40 TS1005 errors and breaking //cdk:compile.
Summary
Full Jira Cloud integration, bringing Jira to parity with the existing Linear adapter: a Jira issue gets the
bgagentlabel → ABCA picks it up → an agent run produces a PR → progress flows back as comments on the originating issue. Implements #288.The Linear adapter was the file-for-file template; this PR diverges only where Jira's API forces it (see Jira-specific divergences below). Design rationale is captured in ADR-014 (
docs/decisions/ADR-014-jira-integration.md).Scope: Jira Cloud only (REST v3 + Cloud webhooks), per-tenant OAuth 3LO, label-only trigger. Inbound-only adapter — no DynamoDB Streams consumer, no outbound-notify Lambda. Out of scope: Jira Server/Data Center, Forge/Connect distribution, status-transition/comment-command triggers, bidirectional state sync.
Related
This PR resolves issue #288 to add a Jira integration
Architecture
Inbound (Jira → ABCA):
Outbound (Agent → Jira): progress comments posted on the originating issue at task start and completion.
What's included
Contracts (Phase 1)
'jira'added to theChannelSourceunion on both sides of the wire (cdk/src/handlers/shared/types.ts,cli/src/types.ts) plus theagent/src/models.pydoc comment.check-types-sync.tsallowlistsJiraLinkResponseas CLI-only (parity withSlack/Linearlink responses).CDK constructs & DynamoDB (Phases 2 & 4)
JiraIntegrationconstruct (cdk/src/constructs/jira-integration.ts) — 3 mapping tables + an 8-hour-TTL webhook-dedup table, 3 Lambdas (webhook / processor / link),/jira/*API routes, per-tenantbgagent-jira-oauth-*IAM grants, and cdk-nag suppressions.cloudIdas the tenant prefix so the same project key / account id stays unambiguous across tenants:JiraProjectMappingTable{cloudId}#{projectKey}→owner/repoJiraUserMappingTable{cloudId}#{accountId}(+PlatformUserIndexGSI)JiraWorkspaceRegistryTablejira_cloud_id→ OAuth provider namecdk/src/stacks/agent.ts): grants the orchestrator read on the workspace registry +Get/PutSecretValueon the per-tenant prefix (mirrors Linear's pre-container failure-feedback path).Lambda handlers & shared helpers (Phase 3)
jira-webhook.ts— HMAC-SHA256 verify over the raw body (X-Hub-Signature: sha256=<hex>, constant-time compare), event filtering (jira:issue_created/jira:issue_updated), dedup, async invoke. Silent 200 for unsupported events.jira-webhook-processor.ts— label-add detection by diffingchangelog.items[](notissue.fields.labels), ADF→markdown for the task description,cloudId→tenant resolution,createTaskCorewithchannelSource: 'jira'.jira-link.ts— Cognito-authenticated Jira-account → platform-user linking with dry-run preview.jira-oauth-resolver.ts(per-tenant token resolve/refresh),jira-verify.ts(signature),jira-feedback.ts(REST-based failure comment at the orchestrator boundary).Agent runtime (Phase 5)
channel_mcp.pyrefactored from a single-channel gate to aCHANNEL_MCP_BUILDERSdispatch dict — adding a channel is now one entry, not a rewrite.resolve_jira_oauth_token()added toconfig.py, mirroring the Linear resolver's race-handling and fail-closed semantics (differs only in endpointauth.atlassian.com+ JSON body +JIRA_API_TOKEN).Outbound progress comments —
agent/src/jira_reactions.py(Phase 7)Jira-origin tasks comment on the issue at start (
🤖 picked up…) and completion (✅ finished — PR: <url>/❌ …), wired intopipeline.pyat start / finish / crash paths, parallel to the Linear hooks. Comments are advisory — all network/auth errors are swallowed and never gate the pipeline (with an auth circuit-breaker mirroringlinear_reactions).CLI (Phase 6)
bgagent jirawith 4 v1 subcommands (app-template,setup,link,map) +jira-oauth.ts(portslinear-oauth.ts; Atlassian uses a JSON token endpoint and requiresoffline_accessfor a refresh token). Deferred:add-workspace,update-webhook-secret,invite-user,list-projects.Docs
JIRA_SETUP_GUIDE.md, ADR-014, README / USER_GUIDE / ROADMAP channel listings, and regenerated Starlight mirrors.Jira-specific divergences from the Linear copy
changelog.items[](field: "labels",fromString/toString), not as a full label list; the processor diffs the changelog so re-saving an already-labeled issue doesn't re-trigger.bgagent jira setup. Stored on the per-tenant OAuth bundle with a stack-wide fallback.cloudIdis the tenant key everywhere — not domain or site name.cloudId— the processor falls back to the soleactivetenant in the registry, and deliberately drops (rather than guesses) when zero or multiple active tenants exist, preserving multi-tenant safety.{issueKey}#{webhookEventTimestamp}, 8-hour TTL.Testing
mise //cdk:compile— clean;check-types-sync.ts— passing (verified on this branch).mise run buildgreen.jira-webhook.test.ts,jira-webhook-processor.test.ts,jira-link.test.ts,jira-oauth-resolver.test.ts(32),test_jira_reactions.py,test_channel_mcp.py(jira cases), plusagent.test.tsupdated for the 4 new tables (13→17).Branch rebased onto the fork's
mainso the diff is Jira-only (46 files); the 4 unrelated upstream commits it was originally cut from have been dropped.Acknowledgment
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of the project license.