@renseiai/plugin-linear 0.8.6
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.
- package/LICENSE +21 -0
- package/README.md +91 -0
- package/dist/src/__tests__/subpath-exports.test.d.ts +2 -0
- package/dist/src/__tests__/subpath-exports.test.d.ts.map +1 -0
- package/dist/src/__tests__/subpath-exports.test.js +136 -0
- package/dist/src/agent-client-project-repo.test.d.ts +2 -0
- package/dist/src/agent-client-project-repo.test.d.ts.map +1 -0
- package/dist/src/agent-client-project-repo.test.js +153 -0
- package/dist/src/agent-client.d.ts +261 -0
- package/dist/src/agent-client.d.ts.map +1 -0
- package/dist/src/agent-client.js +902 -0
- package/dist/src/agent-session.d.ts +303 -0
- package/dist/src/agent-session.d.ts.map +1 -0
- package/dist/src/agent-session.js +969 -0
- package/dist/src/checkbox-utils.d.ts +88 -0
- package/dist/src/checkbox-utils.d.ts.map +1 -0
- package/dist/src/checkbox-utils.js +120 -0
- package/dist/src/circuit-breaker.d.ts +76 -0
- package/dist/src/circuit-breaker.d.ts.map +1 -0
- package/dist/src/circuit-breaker.js +229 -0
- package/dist/src/circuit-breaker.test.d.ts +2 -0
- package/dist/src/circuit-breaker.test.d.ts.map +1 -0
- package/dist/src/circuit-breaker.test.js +292 -0
- package/dist/src/constants.d.ts +87 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +101 -0
- package/dist/src/defaults/auto-trigger.d.ts +35 -0
- package/dist/src/defaults/auto-trigger.d.ts.map +1 -0
- package/dist/src/defaults/auto-trigger.js +36 -0
- package/dist/src/defaults/index.d.ts +12 -0
- package/dist/src/defaults/index.d.ts.map +1 -0
- package/dist/src/defaults/index.js +11 -0
- package/dist/src/defaults/priority.d.ts +20 -0
- package/dist/src/defaults/priority.d.ts.map +1 -0
- package/dist/src/defaults/priority.js +38 -0
- package/dist/src/defaults/prompts.d.ts +42 -0
- package/dist/src/defaults/prompts.d.ts.map +1 -0
- package/dist/src/defaults/prompts.js +313 -0
- package/dist/src/defaults/prompts.test.d.ts +2 -0
- package/dist/src/defaults/prompts.test.d.ts.map +1 -0
- package/dist/src/defaults/prompts.test.js +263 -0
- package/dist/src/defaults/work-type-detection.d.ts +19 -0
- package/dist/src/defaults/work-type-detection.d.ts.map +1 -0
- package/dist/src/defaults/work-type-detection.js +98 -0
- package/dist/src/errors.d.ts +91 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +173 -0
- package/dist/src/frontend-adapter.d.ts +168 -0
- package/dist/src/frontend-adapter.d.ts.map +1 -0
- package/dist/src/frontend-adapter.js +314 -0
- package/dist/src/frontend-adapter.test.d.ts +2 -0
- package/dist/src/frontend-adapter.test.d.ts.map +1 -0
- package/dist/src/frontend-adapter.test.js +545 -0
- package/dist/src/index.d.ts +32 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +35 -0
- package/dist/src/issue-tracker-adapter.d.ts +113 -0
- package/dist/src/issue-tracker-adapter.d.ts.map +1 -0
- package/dist/src/issue-tracker-adapter.js +169 -0
- package/dist/src/issue-tracker-proxy.d.ts +140 -0
- package/dist/src/issue-tracker-proxy.d.ts.map +1 -0
- package/dist/src/issue-tracker-proxy.js +10 -0
- package/dist/src/platform-adapter.d.ts +132 -0
- package/dist/src/platform-adapter.d.ts.map +1 -0
- package/dist/src/platform-adapter.js +260 -0
- package/dist/src/platform-adapter.test.d.ts +2 -0
- package/dist/src/platform-adapter.test.d.ts.map +1 -0
- package/dist/src/platform-adapter.test.js +468 -0
- package/dist/src/proxy-client.d.ts +103 -0
- package/dist/src/proxy-client.d.ts.map +1 -0
- package/dist/src/proxy-client.js +191 -0
- package/dist/src/rate-limiter.d.ts +64 -0
- package/dist/src/rate-limiter.d.ts.map +1 -0
- package/dist/src/rate-limiter.js +163 -0
- package/dist/src/rate-limiter.test.d.ts +2 -0
- package/dist/src/rate-limiter.test.d.ts.map +1 -0
- package/dist/src/rate-limiter.test.js +217 -0
- package/dist/src/retry.d.ts +59 -0
- package/dist/src/retry.d.ts.map +1 -0
- package/dist/src/retry.js +82 -0
- package/dist/src/retry.test.d.ts +2 -0
- package/dist/src/retry.test.d.ts.map +1 -0
- package/dist/src/retry.test.js +266 -0
- package/dist/src/tools/deployment-bridge.d.ts +34 -0
- package/dist/src/tools/deployment-bridge.d.ts.map +1 -0
- package/dist/src/tools/deployment-bridge.js +122 -0
- package/dist/src/tools/linear-plugin.d.ts +23 -0
- package/dist/src/tools/linear-plugin.d.ts.map +1 -0
- package/dist/src/tools/linear-plugin.js +175 -0
- package/dist/src/tools/linear-runner.d.ts +37 -0
- package/dist/src/tools/linear-runner.d.ts.map +1 -0
- package/dist/src/tools/linear-runner.js +810 -0
- package/dist/src/types.d.ts +492 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +148 -0
- package/dist/src/utils.d.ts +52 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/utils.js +277 -0
- package/dist/src/webhook-types.d.ts +308 -0
- package/dist/src/webhook-types.d.ts.map +1 -0
- package/dist/src/webhook-types.js +46 -0
- package/package.json +73 -0
|
@@ -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 @@
|
|
|
1
|
+
{"version":3,"file":"platform-adapter.test.d.ts","sourceRoot":"","sources":["../../src/platform-adapter.test.ts"],"names":[],"mappings":""}
|