@renseiai/agentfactory-linear 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +91 -0
  3. package/dist/src/agent-client-project-repo.test.d.ts +2 -0
  4. package/dist/src/agent-client-project-repo.test.d.ts.map +1 -0
  5. package/dist/src/agent-client-project-repo.test.js +153 -0
  6. package/dist/src/agent-client.d.ts +261 -0
  7. package/dist/src/agent-client.d.ts.map +1 -0
  8. package/dist/src/agent-client.js +902 -0
  9. package/dist/src/agent-session.d.ts +303 -0
  10. package/dist/src/agent-session.d.ts.map +1 -0
  11. package/dist/src/agent-session.js +969 -0
  12. package/dist/src/checkbox-utils.d.ts +88 -0
  13. package/dist/src/checkbox-utils.d.ts.map +1 -0
  14. package/dist/src/checkbox-utils.js +120 -0
  15. package/dist/src/circuit-breaker.d.ts +76 -0
  16. package/dist/src/circuit-breaker.d.ts.map +1 -0
  17. package/dist/src/circuit-breaker.js +229 -0
  18. package/dist/src/circuit-breaker.test.d.ts +2 -0
  19. package/dist/src/circuit-breaker.test.d.ts.map +1 -0
  20. package/dist/src/circuit-breaker.test.js +292 -0
  21. package/dist/src/constants.d.ts +87 -0
  22. package/dist/src/constants.d.ts.map +1 -0
  23. package/dist/src/constants.js +101 -0
  24. package/dist/src/defaults/auto-trigger.d.ts +35 -0
  25. package/dist/src/defaults/auto-trigger.d.ts.map +1 -0
  26. package/dist/src/defaults/auto-trigger.js +36 -0
  27. package/dist/src/defaults/index.d.ts +12 -0
  28. package/dist/src/defaults/index.d.ts.map +1 -0
  29. package/dist/src/defaults/index.js +11 -0
  30. package/dist/src/defaults/priority.d.ts +20 -0
  31. package/dist/src/defaults/priority.d.ts.map +1 -0
  32. package/dist/src/defaults/priority.js +37 -0
  33. package/dist/src/defaults/prompts.d.ts +42 -0
  34. package/dist/src/defaults/prompts.d.ts.map +1 -0
  35. package/dist/src/defaults/prompts.js +310 -0
  36. package/dist/src/defaults/prompts.test.d.ts +2 -0
  37. package/dist/src/defaults/prompts.test.d.ts.map +1 -0
  38. package/dist/src/defaults/prompts.test.js +263 -0
  39. package/dist/src/defaults/work-type-detection.d.ts +19 -0
  40. package/dist/src/defaults/work-type-detection.d.ts.map +1 -0
  41. package/dist/src/defaults/work-type-detection.js +93 -0
  42. package/dist/src/errors.d.ts +91 -0
  43. package/dist/src/errors.d.ts.map +1 -0
  44. package/dist/src/errors.js +173 -0
  45. package/dist/src/frontend-adapter.d.ts +168 -0
  46. package/dist/src/frontend-adapter.d.ts.map +1 -0
  47. package/dist/src/frontend-adapter.js +314 -0
  48. package/dist/src/frontend-adapter.test.d.ts +2 -0
  49. package/dist/src/frontend-adapter.test.d.ts.map +1 -0
  50. package/dist/src/frontend-adapter.test.js +545 -0
  51. package/dist/src/index.d.ts +28 -0
  52. package/dist/src/index.d.ts.map +1 -0
  53. package/dist/src/index.js +30 -0
  54. package/dist/src/issue-tracker-proxy.d.ts +140 -0
  55. package/dist/src/issue-tracker-proxy.d.ts.map +1 -0
  56. package/dist/src/issue-tracker-proxy.js +10 -0
  57. package/dist/src/platform-adapter.d.ts +132 -0
  58. package/dist/src/platform-adapter.d.ts.map +1 -0
  59. package/dist/src/platform-adapter.js +260 -0
  60. package/dist/src/platform-adapter.test.d.ts +2 -0
  61. package/dist/src/platform-adapter.test.d.ts.map +1 -0
  62. package/dist/src/platform-adapter.test.js +468 -0
  63. package/dist/src/proxy-client.d.ts +103 -0
  64. package/dist/src/proxy-client.d.ts.map +1 -0
  65. package/dist/src/proxy-client.js +191 -0
  66. package/dist/src/rate-limiter.d.ts +64 -0
  67. package/dist/src/rate-limiter.d.ts.map +1 -0
  68. package/dist/src/rate-limiter.js +163 -0
  69. package/dist/src/rate-limiter.test.d.ts +2 -0
  70. package/dist/src/rate-limiter.test.d.ts.map +1 -0
  71. package/dist/src/rate-limiter.test.js +217 -0
  72. package/dist/src/retry.d.ts +59 -0
  73. package/dist/src/retry.d.ts.map +1 -0
  74. package/dist/src/retry.js +82 -0
  75. package/dist/src/types.d.ts +492 -0
  76. package/dist/src/types.d.ts.map +1 -0
  77. package/dist/src/types.js +143 -0
  78. package/dist/src/utils.d.ts +52 -0
  79. package/dist/src/utils.d.ts.map +1 -0
  80. package/dist/src/utils.js +277 -0
  81. package/dist/src/webhook-types.d.ts +308 -0
  82. package/dist/src/webhook-types.d.ts.map +1 -0
  83. package/dist/src/webhook-types.js +46 -0
  84. package/package.json +70 -0
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Platform-Agnostic Issue Tracker Proxy Interface
3
+ *
4
+ * Defines the contract between agents/governors and the centralized proxy.
5
+ * Agents call these methods without knowing whether the backend is Linear,
6
+ * Jira, GitHub Issues, or any future platform.
7
+ *
8
+ * The proxy translates these generic operations into platform-specific API calls.
9
+ */
10
+ /**
11
+ * Every proxy request carries authentication and workspace context.
12
+ */
13
+ export interface ProxyRequest {
14
+ /** The operation to perform */
15
+ method: IssueTrackerMethod;
16
+ /** Arguments for the operation (method-specific) */
17
+ args: unknown[];
18
+ /** Workspace/organization ID for multi-tenant routing */
19
+ organizationId?: string;
20
+ }
21
+ /**
22
+ * Standard proxy response envelope.
23
+ */
24
+ export interface ProxyResponse<T = unknown> {
25
+ success: boolean;
26
+ data?: T;
27
+ error?: {
28
+ code: string;
29
+ message: string;
30
+ retryable: boolean;
31
+ };
32
+ /** Rate limit info from the upstream platform */
33
+ quota?: {
34
+ requestsRemaining?: number;
35
+ complexityRemaining?: number;
36
+ resetAt?: number;
37
+ };
38
+ }
39
+ /**
40
+ * All operations that can be routed through the proxy.
41
+ *
42
+ * Named generically (not Linear-specific) so future platforms
43
+ * can implement the same interface.
44
+ */
45
+ export type IssueTrackerMethod = 'getIssue' | 'updateIssue' | 'createIssue' | 'createComment' | 'getIssueComments' | 'getTeamStatuses' | 'updateIssueStatus' | 'createAgentActivity' | 'updateAgentSession' | 'createAgentSessionOnIssue' | 'createIssueRelation' | 'getIssueRelations' | 'deleteIssueRelation' | 'getSubIssues' | 'getSubIssueStatuses' | 'getSubIssueGraph' | 'isParentIssue' | 'isChildIssue' | 'listProjectIssues' | 'getProjectRepositoryUrl' | 'getViewer' | 'getTeam' | 'unassignIssue';
46
+ /**
47
+ * Serialized issue — all relations pre-resolved to plain JSON.
48
+ * No SDK-specific Promise fields or lazy loaders.
49
+ */
50
+ export interface SerializedIssue {
51
+ id: string;
52
+ identifier: string;
53
+ title: string;
54
+ description?: string;
55
+ url: string;
56
+ priority: number;
57
+ state?: {
58
+ id: string;
59
+ name: string;
60
+ type: string;
61
+ };
62
+ labels: Array<{
63
+ id: string;
64
+ name: string;
65
+ }>;
66
+ assignee?: {
67
+ id: string;
68
+ name: string;
69
+ email?: string;
70
+ } | null;
71
+ team?: {
72
+ id: string;
73
+ name: string;
74
+ key: string;
75
+ };
76
+ parent?: {
77
+ id: string;
78
+ identifier: string;
79
+ } | null;
80
+ project?: {
81
+ id: string;
82
+ name: string;
83
+ } | null;
84
+ createdAt: string;
85
+ updatedAt: string;
86
+ }
87
+ /**
88
+ * Serialized comment.
89
+ */
90
+ export interface SerializedComment {
91
+ id: string;
92
+ body: string;
93
+ createdAt: string;
94
+ updatedAt: string;
95
+ user?: {
96
+ id: string;
97
+ name: string;
98
+ } | null;
99
+ }
100
+ /**
101
+ * Serialized viewer (authenticated user).
102
+ */
103
+ export interface SerializedViewer {
104
+ id: string;
105
+ name: string;
106
+ email: string;
107
+ }
108
+ /**
109
+ * Serialized team.
110
+ */
111
+ export interface SerializedTeam {
112
+ id: string;
113
+ name: string;
114
+ key: string;
115
+ }
116
+ /**
117
+ * Health status for the proxy endpoint.
118
+ */
119
+ export interface ProxyHealthStatus {
120
+ /** Whether the proxy can process requests */
121
+ healthy: boolean;
122
+ /** Circuit breaker state */
123
+ circuitBreaker: {
124
+ state: string;
125
+ failures: number;
126
+ msSinceOpened: number | null;
127
+ };
128
+ /** Rate limiter status */
129
+ rateLimiter: {
130
+ availableTokens: number;
131
+ };
132
+ /** Upstream platform quota from response headers */
133
+ quota: {
134
+ requestsRemaining: number | null;
135
+ complexityRemaining: number | null;
136
+ resetAt: number | null;
137
+ updatedAt: number;
138
+ };
139
+ }
140
+ //# sourceMappingURL=issue-tracker-proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issue-tracker-proxy.d.ts","sourceRoot":"","sources":["../../src/issue-tracker-proxy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Platform-Agnostic Issue Tracker Proxy Interface
3
+ *
4
+ * Defines the contract between agents/governors and the centralized proxy.
5
+ * Agents call these methods without knowing whether the backend is Linear,
6
+ * Jira, GitHub Issues, or any future platform.
7
+ *
8
+ * The proxy translates these generic operations into platform-specific API calls.
9
+ */
10
+ export {};
@@ -0,0 +1,132 @@
1
+ /**
2
+ * LinearPlatformAdapter
3
+ *
4
+ * Extends LinearFrontendAdapter with Governor event integration methods.
5
+ * Structurally satisfies the PlatformAdapter interface from @renseiai/agentfactory
6
+ * without an explicit `implements` clause, avoiding a circular package dependency
7
+ * (core depends on linear, so linear cannot import core).
8
+ *
9
+ * Responsibilities:
10
+ * - Normalize Linear webhook payloads into GovernorEvents
11
+ * - Scan Linear projects for non-terminal issues
12
+ * - Convert Linear SDK Issue objects to GovernorIssue
13
+ */
14
+ import { LinearFrontendAdapter } from './frontend-adapter.js';
15
+ import type { LinearAgentClient } from './agent-client.js';
16
+ /**
17
+ * Minimal issue representation used by the Governor.
18
+ * Structurally identical to GovernorIssue in @renseiai/agentfactory.
19
+ */
20
+ export interface GovernorIssue {
21
+ id: string;
22
+ identifier: string;
23
+ title: string;
24
+ description?: string;
25
+ status: string;
26
+ labels: string[];
27
+ createdAt: number;
28
+ parentId?: string;
29
+ project?: string;
30
+ }
31
+ /** Where an event originated. */
32
+ type EventSource = 'webhook' | 'poll' | 'manual';
33
+ /**
34
+ * Fired when an issue's workflow status changes.
35
+ * Structurally identical to IssueStatusChangedEvent in @renseiai/agentfactory.
36
+ */
37
+ interface IssueStatusChangedEvent {
38
+ type: 'issue-status-changed';
39
+ issueId: string;
40
+ issue: GovernorIssue;
41
+ previousStatus?: string;
42
+ newStatus: string;
43
+ timestamp: string;
44
+ source: EventSource;
45
+ }
46
+ /**
47
+ * Fired when a comment is added to an issue.
48
+ * Structurally identical to CommentAddedEvent in @renseiai/agentfactory.
49
+ */
50
+ interface CommentAddedEvent {
51
+ type: 'comment-added';
52
+ issueId: string;
53
+ issue: GovernorIssue;
54
+ commentId: string;
55
+ commentBody: string;
56
+ userId?: string;
57
+ userName?: string;
58
+ timestamp: string;
59
+ source: EventSource;
60
+ }
61
+ /** Union of events this adapter can produce. */
62
+ type GovernorEvent = IssueStatusChangedEvent | CommentAddedEvent;
63
+ /**
64
+ * Linear platform adapter for the EventDrivenGovernor.
65
+ *
66
+ * Extends LinearFrontendAdapter to inherit all frontend operations
67
+ * (status mapping, issue read/write, agent sessions) and adds
68
+ * Governor-specific methods for webhook normalization, project scanning,
69
+ * and issue conversion.
70
+ *
71
+ * Structurally satisfies PlatformAdapter from @renseiai/agentfactory.
72
+ */
73
+ export declare class LinearPlatformAdapter extends LinearFrontendAdapter {
74
+ /**
75
+ * The underlying Linear client, exposed to subclass methods.
76
+ * Re-declared here because the parent's `client` field is private.
77
+ */
78
+ private readonly linearAgentClient;
79
+ constructor(client: LinearAgentClient);
80
+ /**
81
+ * Normalize a raw Linear webhook payload into GovernorEvents.
82
+ *
83
+ * Handles two payload types:
84
+ * - Issue updates with state changes -> IssueStatusChangedEvent
85
+ * - Comment creations -> CommentAddedEvent
86
+ *
87
+ * Returns `null` for unrecognized payloads (e.g., label changes,
88
+ * AgentSession events, or other resource types).
89
+ *
90
+ * @param payload - Raw Linear webhook payload
91
+ * @returns Array of GovernorEvents, or null if not relevant
92
+ */
93
+ normalizeWebhookEvent(payload: unknown): GovernorEvent[] | null;
94
+ /**
95
+ * Scan a Linear project for all non-terminal issues.
96
+ *
97
+ * Uses a single GraphQL query via `listProjectIssues()` to fetch all
98
+ * issue data in one API call, eliminating the N+1 problem of lazy-loading
99
+ * state/labels/parent/project for each issue.
100
+ *
101
+ * @param project - Linear project name to scan
102
+ * @returns Array of GovernorIssue for all active issues
103
+ */
104
+ scanProjectIssues(project: string): Promise<GovernorIssue[]>;
105
+ /**
106
+ * Scan a project and return both GovernorIssues and a set of parent issue IDs.
107
+ *
108
+ * The parent issue IDs are derived from `childCount > 0` in the single
109
+ * GraphQL query, allowing callers to skip per-issue `isParentIssue()` API calls.
110
+ *
111
+ * @param project - Linear project name to scan
112
+ * @returns Issues and a set of parent issue IDs
113
+ */
114
+ scanProjectIssuesWithParents(project: string): Promise<{
115
+ issues: GovernorIssue[];
116
+ parentIssueIds: Set<string>;
117
+ }>;
118
+ /**
119
+ * Convert a Linear SDK Issue object to a GovernorIssue.
120
+ *
121
+ * The `native` parameter is typed as `unknown` to satisfy the
122
+ * PlatformAdapter interface. Internally it is cast to the Linear SDK
123
+ * `Issue` type. Callers must ensure they pass a valid Linear Issue.
124
+ *
125
+ * @param native - Linear SDK Issue object
126
+ * @returns GovernorIssue representation
127
+ * @throws Error if the native object is not a valid Linear Issue
128
+ */
129
+ toGovernorIssue(native: unknown): Promise<GovernorIssue>;
130
+ }
131
+ export {};
132
+ //# sourceMappingURL=platform-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform-adapter.d.ts","sourceRoot":"","sources":["../../src/platform-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAY1D;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,iCAAiC;AACjC,KAAK,WAAW,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAA;AAEhD;;;GAGG;AACH,UAAU,uBAAuB;IAC/B,IAAI,EAAE,sBAAsB,CAAA;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,aAAa,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,WAAW,CAAA;CACpB;AAED;;;GAGG;AACH,UAAU,iBAAiB;IACzB,IAAI,EAAE,eAAe,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,aAAa,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,WAAW,CAAA;CACpB;AAED,gDAAgD;AAChD,KAAK,aAAa,GAAG,uBAAuB,GAAG,iBAAiB,CAAA;AAmJhE;;;;;;;;;GASG;AACH,qBAAa,qBAAsB,SAAQ,qBAAqB;IAC9D;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAmB;gBAEzC,MAAM,EAAE,iBAAiB;IAOrC;;;;;;;;;;;;OAYG;IACH,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,EAAE,GAAG,IAAI;IA0D/D;;;;;;;;;OASG;IACG,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAgBlE;;;;;;;;OAQG;IACG,4BAA4B,CAChC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;QAAE,MAAM,EAAE,aAAa,EAAE,CAAC;QAAC,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;KAAE,CAAC;IA0BpE;;;;;;;;;;OAUG;IACG,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC;CAS/D"}
@@ -0,0 +1,260 @@
1
+ /**
2
+ * LinearPlatformAdapter
3
+ *
4
+ * Extends LinearFrontendAdapter with Governor event integration methods.
5
+ * Structurally satisfies the PlatformAdapter interface from @renseiai/agentfactory
6
+ * without an explicit `implements` clause, avoiding a circular package dependency
7
+ * (core depends on linear, so linear cannot import core).
8
+ *
9
+ * Responsibilities:
10
+ * - Normalize Linear webhook payloads into GovernorEvents
11
+ * - Scan Linear projects for non-terminal issues
12
+ * - Convert Linear SDK Issue objects to GovernorIssue
13
+ */
14
+ import { LinearFrontendAdapter } from './frontend-adapter.js';
15
+ // ---------------------------------------------------------------------------
16
+ // Terminal statuses
17
+ // ---------------------------------------------------------------------------
18
+ /**
19
+ * Statuses that represent terminal states in Linear.
20
+ * Issues in these states are excluded from project scans.
21
+ */
22
+ const TERMINAL_STATUSES = ['Accepted', 'Canceled', 'Duplicate'];
23
+ // ---------------------------------------------------------------------------
24
+ // Helpers
25
+ // ---------------------------------------------------------------------------
26
+ /**
27
+ * Generate an ISO-8601 timestamp for the current moment.
28
+ * Equivalent to `eventTimestamp()` from @renseiai/agentfactory.
29
+ */
30
+ function eventTimestamp() {
31
+ return new Date().toISOString();
32
+ }
33
+ /**
34
+ * Build a GovernorIssue from Linear webhook issue data.
35
+ * Does not make API calls -- uses only the data present in the webhook payload.
36
+ */
37
+ function webhookIssueToGovernorIssue(issueData) {
38
+ return {
39
+ id: issueData.id,
40
+ identifier: issueData.identifier,
41
+ title: issueData.title,
42
+ description: issueData.description ?? undefined,
43
+ status: issueData.state?.name ?? 'Backlog',
44
+ labels: issueData.labels?.map((l) => l.name) ?? [],
45
+ createdAt: issueData.createdAt
46
+ ? new Date(issueData.createdAt).getTime()
47
+ : Date.now(),
48
+ parentId: issueData.parent?.id,
49
+ project: issueData.project?.name,
50
+ };
51
+ }
52
+ /**
53
+ * Convert a Linear SDK Issue object to a GovernorIssue.
54
+ * Resolves lazy-loaded relations (state, labels, parent, project).
55
+ */
56
+ async function sdkIssueToGovernorIssue(issue) {
57
+ const state = await issue.state;
58
+ const labels = await issue.labels();
59
+ const parent = await issue.parent;
60
+ const project = await issue.project;
61
+ return {
62
+ id: issue.id,
63
+ identifier: issue.identifier,
64
+ title: issue.title,
65
+ description: issue.description ?? undefined,
66
+ status: state?.name ?? 'Backlog',
67
+ labels: labels.nodes.map((l) => l.name),
68
+ createdAt: issue.createdAt.getTime(),
69
+ parentId: parent?.id,
70
+ project: project?.name,
71
+ };
72
+ }
73
+ // ---------------------------------------------------------------------------
74
+ // Type guards for webhook payloads
75
+ // ---------------------------------------------------------------------------
76
+ /**
77
+ * Check if a payload looks like a Linear issue webhook.
78
+ */
79
+ function isIssuePayload(payload) {
80
+ if (typeof payload !== 'object' || payload === null)
81
+ return false;
82
+ const p = payload;
83
+ return (p.type === 'Issue' &&
84
+ typeof p.action === 'string' &&
85
+ typeof p.data === 'object' &&
86
+ p.data !== null);
87
+ }
88
+ /**
89
+ * Check if a payload looks like a Linear comment webhook.
90
+ */
91
+ function isCommentPayload(payload) {
92
+ if (typeof payload !== 'object' || payload === null)
93
+ return false;
94
+ const p = payload;
95
+ return (p.type === 'Comment' &&
96
+ typeof p.action === 'string' &&
97
+ typeof p.data === 'object' &&
98
+ p.data !== null);
99
+ }
100
+ // ---------------------------------------------------------------------------
101
+ // LinearPlatformAdapter
102
+ // ---------------------------------------------------------------------------
103
+ /**
104
+ * Linear platform adapter for the EventDrivenGovernor.
105
+ *
106
+ * Extends LinearFrontendAdapter to inherit all frontend operations
107
+ * (status mapping, issue read/write, agent sessions) and adds
108
+ * Governor-specific methods for webhook normalization, project scanning,
109
+ * and issue conversion.
110
+ *
111
+ * Structurally satisfies PlatformAdapter from @renseiai/agentfactory.
112
+ */
113
+ export class LinearPlatformAdapter extends LinearFrontendAdapter {
114
+ /**
115
+ * The underlying Linear client, exposed to subclass methods.
116
+ * Re-declared here because the parent's `client` field is private.
117
+ */
118
+ linearAgentClient;
119
+ constructor(client) {
120
+ super(client);
121
+ this.linearAgentClient = client;
122
+ }
123
+ // ---- PlatformAdapter methods ----
124
+ /**
125
+ * Normalize a raw Linear webhook payload into GovernorEvents.
126
+ *
127
+ * Handles two payload types:
128
+ * - Issue updates with state changes -> IssueStatusChangedEvent
129
+ * - Comment creations -> CommentAddedEvent
130
+ *
131
+ * Returns `null` for unrecognized payloads (e.g., label changes,
132
+ * AgentSession events, or other resource types).
133
+ *
134
+ * @param payload - Raw Linear webhook payload
135
+ * @returns Array of GovernorEvents, or null if not relevant
136
+ */
137
+ normalizeWebhookEvent(payload) {
138
+ // Handle Issue update with state change
139
+ if (isIssuePayload(payload)) {
140
+ // Only handle updates (not creates/removes)
141
+ if (payload.action !== 'update')
142
+ return null;
143
+ // Only produce an event if the state actually changed
144
+ const hasStateChange = payload.updatedFrom?.stateId !== undefined;
145
+ if (!hasStateChange)
146
+ return null;
147
+ const issue = webhookIssueToGovernorIssue(payload.data);
148
+ const event = {
149
+ type: 'issue-status-changed',
150
+ issueId: payload.data.id,
151
+ issue,
152
+ newStatus: issue.status,
153
+ // Previous status name is not directly available from stateId alone;
154
+ // we only know the stateId changed. The previous status name would
155
+ // require an API call, so we leave it undefined.
156
+ previousStatus: undefined,
157
+ timestamp: eventTimestamp(),
158
+ source: 'webhook',
159
+ };
160
+ return [event];
161
+ }
162
+ // Handle Comment creation
163
+ if (isCommentPayload(payload)) {
164
+ if (payload.action !== 'create')
165
+ return null;
166
+ const commentData = payload.data;
167
+ const issueData = commentData.issue;
168
+ if (!issueData)
169
+ return null;
170
+ const issue = webhookIssueToGovernorIssue(issueData);
171
+ const event = {
172
+ type: 'comment-added',
173
+ issueId: issueData.id,
174
+ issue,
175
+ commentId: commentData.id,
176
+ commentBody: commentData.body,
177
+ userId: commentData.user?.id,
178
+ userName: commentData.user?.name,
179
+ timestamp: eventTimestamp(),
180
+ source: 'webhook',
181
+ };
182
+ return [event];
183
+ }
184
+ // Unrecognized payload type
185
+ return null;
186
+ }
187
+ /**
188
+ * Scan a Linear project for all non-terminal issues.
189
+ *
190
+ * Uses a single GraphQL query via `listProjectIssues()` to fetch all
191
+ * issue data in one API call, eliminating the N+1 problem of lazy-loading
192
+ * state/labels/parent/project for each issue.
193
+ *
194
+ * @param project - Linear project name to scan
195
+ * @returns Array of GovernorIssue for all active issues
196
+ */
197
+ async scanProjectIssues(project) {
198
+ const issues = await this.linearAgentClient.listProjectIssues(project);
199
+ return issues.map((issue) => ({
200
+ id: issue.id,
201
+ identifier: issue.identifier,
202
+ title: issue.title,
203
+ description: issue.description,
204
+ status: issue.status,
205
+ labels: issue.labels,
206
+ createdAt: issue.createdAt,
207
+ parentId: issue.parentId,
208
+ project: issue.project,
209
+ }));
210
+ }
211
+ /**
212
+ * Scan a project and return both GovernorIssues and a set of parent issue IDs.
213
+ *
214
+ * The parent issue IDs are derived from `childCount > 0` in the single
215
+ * GraphQL query, allowing callers to skip per-issue `isParentIssue()` API calls.
216
+ *
217
+ * @param project - Linear project name to scan
218
+ * @returns Issues and a set of parent issue IDs
219
+ */
220
+ async scanProjectIssuesWithParents(project) {
221
+ const rawIssues = await this.linearAgentClient.listProjectIssues(project);
222
+ const parentIssueIds = new Set();
223
+ const issues = [];
224
+ for (const issue of rawIssues) {
225
+ if (issue.childCount > 0) {
226
+ parentIssueIds.add(issue.id);
227
+ }
228
+ issues.push({
229
+ id: issue.id,
230
+ identifier: issue.identifier,
231
+ title: issue.title,
232
+ description: issue.description,
233
+ status: issue.status,
234
+ labels: issue.labels,
235
+ createdAt: issue.createdAt,
236
+ parentId: issue.parentId,
237
+ project: issue.project,
238
+ });
239
+ }
240
+ return { issues, parentIssueIds };
241
+ }
242
+ /**
243
+ * Convert a Linear SDK Issue object to a GovernorIssue.
244
+ *
245
+ * The `native` parameter is typed as `unknown` to satisfy the
246
+ * PlatformAdapter interface. Internally it is cast to the Linear SDK
247
+ * `Issue` type. Callers must ensure they pass a valid Linear Issue.
248
+ *
249
+ * @param native - Linear SDK Issue object
250
+ * @returns GovernorIssue representation
251
+ * @throws Error if the native object is not a valid Linear Issue
252
+ */
253
+ async toGovernorIssue(native) {
254
+ const issue = native;
255
+ if (!issue || typeof issue.id !== 'string') {
256
+ throw new Error('LinearPlatformAdapter.toGovernorIssue: expected a Linear SDK Issue object');
257
+ }
258
+ return sdkIssueToGovernorIssue(issue);
259
+ }
260
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=platform-adapter.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform-adapter.test.d.ts","sourceRoot":"","sources":["../../src/platform-adapter.test.ts"],"names":[],"mappings":""}