feat(pm): add Linear PM provider adapter and integration#1096
Conversation
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
nhopeatall
left a comment
There was a problem hiding this comment.
Summary
Solid implementation that follows existing patterns (JIRA adapter/integration). Three issues need fixing before merge: LinearIntegration is never registered in bootstrap.ts, addChecklistItem creates standalone issues instead of sub-issues, and isSelfAuthored will never work because it calls linearClient.getMe() without credentials in scope.
Architecture & Design
-
[BLOCKING] Missing bootstrap registration:
LinearIntegrationis not registered insrc/integrations/bootstrap.ts. Without this,pmRegistry.get('linear')will throw at runtime when a Linear project receives a webhook or a worker tries to create a provider. The self-registration viaregisterCredentialRoles()at module load handles credential roles only, but the actual integration object is never instantiated and registered intopmRegistryorintegrationRegistry. Add a block following the Trello/JIRA pattern in bootstrap.ts. -
[SHOULD_FIX]
getPromptTerminology()doesn't handle Linear:src/agents/shared/promptContext.ts:23-32uses a binaryisJiracheck — Linear projects will get Trello terminology (card,cards,Trello) instead ofissue,issues,Linear. This will cause confusing agent prompts for Linear projects. -
[SHOULD_FIX]
getListIds()doesn't read Linear config:src/agents/shared/promptContext.ts:6-21only reads Trello and JIRA configs. Linear projects won't have status IDs populated in prompt context, which means lifecycle status references in agent prompts will be undefined.
Code Issues
Blocking
-
src/pm/linear/adapter.ts:201 —
addChecklistItemextractsparentIdfrom the checklist ID but never passes it tocreateIssue(). TheLinearCreateIssueInputtype doesn't includeparentId, so the created issue is standalone with no parent relationship. Either addparentIdto the type and pass it, or document as a known limitation. -
src/pm/linear/integration.ts:148 —
isSelfAuthoredcallslinearClient.getMe()withoutwithLinearCredentials(). This always throws (caught silently), meaning the bot can never detect its own comments — feedback loop risk. The_projectIdparameter (unused, underscore-prefixed) should be used to scope credentials.
Should Fix
-
src/pm/linear/adapter.ts:23 — Local
LinearConfigtype haslabels?: Record<string, string>butsrc/pm/config.tshaslabels?: { processing?: string; ... }. Import the canonical type instead of redeclaring. -
src/pm/linear/integration.ts:186 —
sendReactionis a no-op but extractscommentIdand checks event type before returning. Either implement usinglinearClient.createReaction()or remove the dead code. -
src/router/config.ts:84-103 —
loadProjectConfigonly populates Trello and JIRA config inRouterProjectConfig. Linear projects will havepmType: 'linear'but no provider-specific config.
🕵️ claude-code · claude-opus-4-6 · run details
Inline Review Notes
await linearClient.createIssue({
teamId: this.config.teamId,
title: name,
description,
parentId, // <-- missing
});
async isSelfAuthored(event: PMWebhookEvent, projectId: string): Promise<boolean> {
// ...
const apiKey = await getIntegrationCredential(projectId, 'pm', 'api_key');
const me = await withLinearCredentials({ apiKey }, () => linearClient.getMe());
return me.id === commentUserId;
}
The local 🕵️ claude-code · claude-opus-4-6 · run details |
- Register LinearIntegration in bootstrap.ts following Trello/JIRA pattern - Fix addChecklistItem to pass parentId to createIssue() for proper sub-issue relationship - Add parentId to LinearCreateIssueInput type in linear/types.ts - Fix isSelfAuthored to use withLinearCredentials() with project credentials - Remove dead code from sendReaction (no-op with dead extraction logic) - Import canonical LinearConfig from pm/config.ts instead of redeclaring local type - Fix getPromptTerminology() to return issue/Linear for Linear projects - Fix getListIds() to read Linear statuses config for prompt context - Add Linear config to RouterProjectConfig and loadProjectConfig() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
nhopeatall
left a comment
There was a problem hiding this comment.
Summary
Clean, well-structured implementation that follows the established JIRA integration pattern closely. All CI checks pass. The adapter and integration files are consistent with existing patterns and the code handles edge cases (null users, missing config, empty identifiers) correctly.
Should Fix
-
src/agents/prompts/index.ts:309 —
getTemplateVariables()description says'PM type: trello or jira'but thepmTypefield now also accepts'linear'. Minor doc inconsistency. Should saytrello, jira, or linearto match. -
src/triggers/github/utils.ts:96-102 — Not part of this PR's diff, but the
extractWorkItemId()function only handlesjiraand falls through to Trello for all other PM types. For Linear projects, this would attempt Trello URL extraction. This function appears to be dead code (no imports found), but if it's ever reactivated, Linear projects would silently fail to extract work item IDs from PR bodies. Worth a follow-up to either delete this dead code or add Linear support.
Notes
- The
getWorkItemUrl()returninghttps://linear.app/issue/<id>(missing workspace slug) is acknowledged in the PR description as a known limitation — the actualissue.urlfromgetWorkItem()is always correct. Acceptable trade-off. addChecklistItemregex correctly handles Linear identifiers containing hyphens (e.g.,TEAM-123), matching the same pattern used by JIRA.- The
extractWorkItemIdregex matches the identical regex in JIRA's integration, which is correct since both use the same identifier format. The system routes to the correct integration'sextractWorkItemIdbased on project PM type, so no collision risk. - Dual registration of credential roles (hardcoded in
integrationRoles.ts+ self-register inintegration.tsmodule load) is intentional and consistent with the PR description's "Credential self-registration" decision.
🕵️ claude-code · claude-opus-4-6 · run details
Summary
LinearPMProviderinsrc/pm/linear/adapter.ts— a fullPMProviderimplementation wrapping the existing Linear GraphQL clientLinearIntegrationinsrc/pm/linear/integration.ts— a fullPMIntegrationimplementation with credential scoping, webhook parsing, ack comments, reactions, project lookup, and work item ID extractionPMTypeunion insrc/pm/types.tsto include'linear'pm.typeenum insrc/config/schema.tstoz.enum(['trello', 'jira', 'linear'])LinearConfigtype andgetLinearConfig()accessor insrc/pm/config.tsLINEAR_API_KEY(required) andLINEAR_WEBHOOK_SECRET(optional) credential roles insrc/config/integrationRoles.tsLinearPMProviderfromsrc/pm/index.tsRouterProjectConfig.pmType,ProjectContext.pmType, andPromptContext.pmTypeto include'linear'tests/unit/pm/linear/adapter.test.ts(33 tests) andtests/unit/pm/linear/integration.test.ts(32 tests)Trello card: https://trello.com/c/69de7f4c5b9eca332747b69d
Key decisions
createChecklist()returns a synthetic checklist object;addChecklistItem()creates a child issue vialinearClient.createIssue()LinearIntegrationcallsregisterCredentialRoles('linear', 'pm', [...])at module load time (idempotent), and the roles are also pre-registered in the hardcoded_rolesRegistryinintegrationRoles.tsfor consistencylookupProject()returns null: Linear-by-teamId project lookup requires a DB repository function not yet implemented (separate story). Returning null falls through to other lookup mechanismsgetWorkItemUrl()constructs generic URL: Useshttps://linear.app/issue/<id>since the full org-scoped URL requires fetching the issue from the API. The actual issue.url fromgetWorkItem()is always correctaddAttachmentFile()posts a comment as a fallback, mirroring the JIRA pattern for URLsTest plan
npm run typecheck)npm run lint)LinearPMProvideradapter tests cover all PMProvider methodsLinearIntegrationtests coverhasIntegration,createProvider,withCredentials,resolveLifecycleConfig,parseWebhookPayload,isSelfAuthored,lookupProject, andextractWorkItemId🤖 Generated with Claude Code
🕵️ claude-code · claude-sonnet-4-6 · run details