chief-clancy 0.2.0-beta.3 → 0.3.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.
- package/README.md +13 -24
- package/dist/installer/file-ops/file-ops.d.ts +32 -0
- package/dist/installer/file-ops/file-ops.d.ts.map +1 -0
- package/dist/installer/file-ops/file-ops.js +58 -0
- package/dist/installer/file-ops/file-ops.js.map +1 -0
- package/dist/installer/hook-installer/hook-installer.d.ts +29 -0
- package/dist/installer/hook-installer/hook-installer.d.ts.map +1 -0
- package/dist/installer/hook-installer/hook-installer.js +96 -0
- package/dist/installer/hook-installer/hook-installer.js.map +1 -0
- package/dist/installer/install.d.ts +3 -0
- package/dist/installer/install.d.ts.map +1 -0
- package/dist/installer/install.js +227 -0
- package/dist/installer/install.js.map +1 -0
- package/dist/installer/manifest/manifest.d.ts +41 -0
- package/dist/installer/manifest/manifest.d.ts.map +1 -0
- package/dist/installer/manifest/manifest.js +97 -0
- package/dist/installer/manifest/manifest.js.map +1 -0
- package/dist/installer/prompts/prompts.d.ts +33 -0
- package/dist/installer/prompts/prompts.d.ts.map +1 -0
- package/dist/installer/prompts/prompts.js +55 -0
- package/dist/installer/prompts/prompts.js.map +1 -0
- package/dist/schemas/env.d.ts +75 -0
- package/dist/schemas/env.d.ts.map +1 -0
- package/dist/schemas/env.js +40 -0
- package/dist/schemas/env.js.map +1 -0
- package/dist/schemas/github.d.ts +27 -0
- package/dist/schemas/github.d.ts.map +1 -0
- package/dist/schemas/github.js +17 -0
- package/dist/schemas/github.js.map +1 -0
- package/dist/schemas/index.d.ts +9 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +5 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/jira.d.ts +37 -0
- package/dist/schemas/jira.d.ts.map +1 -0
- package/dist/schemas/jira.js +37 -0
- package/dist/schemas/jira.js.map +1 -0
- package/dist/schemas/linear.d.ts +67 -0
- package/dist/schemas/linear.d.ts.map +1 -0
- package/dist/schemas/linear.js +50 -0
- package/dist/schemas/linear.js.map +1 -0
- package/dist/scripts/afk/afk.d.ts +21 -0
- package/dist/scripts/afk/afk.d.ts.map +1 -0
- package/dist/scripts/afk/afk.js +116 -0
- package/dist/scripts/afk/afk.js.map +1 -0
- package/dist/scripts/board/github/github.d.ts +56 -0
- package/dist/scripts/board/github/github.d.ts.map +1 -0
- package/dist/scripts/board/github/github.js +142 -0
- package/dist/scripts/board/github/github.js.map +1 -0
- package/dist/scripts/board/jira/jira.d.ts +90 -0
- package/dist/scripts/board/jira/jira.d.ts.map +1 -0
- package/dist/scripts/board/jira/jira.js +251 -0
- package/dist/scripts/board/jira/jira.js.map +1 -0
- package/dist/scripts/board/linear/linear.d.ts +85 -0
- package/dist/scripts/board/linear/linear.d.ts.map +1 -0
- package/dist/scripts/board/linear/linear.js +209 -0
- package/dist/scripts/board/linear/linear.js.map +1 -0
- package/dist/scripts/once/once.d.ts +12 -0
- package/dist/scripts/once/once.d.ts.map +1 -0
- package/dist/scripts/once/once.js +323 -0
- package/dist/scripts/once/once.js.map +1 -0
- package/dist/scripts/shared/branch/branch.d.ts +50 -0
- package/dist/scripts/shared/branch/branch.d.ts.map +1 -0
- package/dist/scripts/shared/branch/branch.js +61 -0
- package/dist/scripts/shared/branch/branch.js.map +1 -0
- package/dist/scripts/shared/claude-cli/claude-cli.d.ts +17 -0
- package/dist/scripts/shared/claude-cli/claude-cli.d.ts.map +1 -0
- package/dist/scripts/shared/claude-cli/claude-cli.js +35 -0
- package/dist/scripts/shared/claude-cli/claude-cli.js.map +1 -0
- package/dist/scripts/shared/env-parser/env-parser.d.ts +30 -0
- package/dist/scripts/shared/env-parser/env-parser.d.ts.map +1 -0
- package/dist/scripts/shared/env-parser/env-parser.js +64 -0
- package/dist/scripts/shared/env-parser/env-parser.js.map +1 -0
- package/dist/scripts/shared/env-schema/env-schema.d.ts +27 -0
- package/dist/scripts/shared/env-schema/env-schema.d.ts.map +1 -0
- package/dist/scripts/shared/env-schema/env-schema.js +46 -0
- package/dist/scripts/shared/env-schema/env-schema.js.map +1 -0
- package/dist/scripts/shared/git-ops/git-ops.d.ts +52 -0
- package/dist/scripts/shared/git-ops/git-ops.d.ts.map +1 -0
- package/dist/scripts/shared/git-ops/git-ops.js +107 -0
- package/dist/scripts/shared/git-ops/git-ops.js.map +1 -0
- package/dist/scripts/shared/http/http.d.ts +52 -0
- package/dist/scripts/shared/http/http.d.ts.map +1 -0
- package/dist/scripts/shared/http/http.js +74 -0
- package/dist/scripts/shared/http/http.js.map +1 -0
- package/dist/scripts/shared/notify/notify.d.ts +46 -0
- package/dist/scripts/shared/notify/notify.d.ts.map +1 -0
- package/dist/scripts/shared/notify/notify.js +88 -0
- package/dist/scripts/shared/notify/notify.js.map +1 -0
- package/dist/scripts/shared/preflight/preflight.d.ts +40 -0
- package/dist/scripts/shared/preflight/preflight.d.ts.map +1 -0
- package/dist/scripts/shared/preflight/preflight.js +84 -0
- package/dist/scripts/shared/preflight/preflight.js.map +1 -0
- package/dist/scripts/shared/progress/progress.d.ts +25 -0
- package/dist/scripts/shared/progress/progress.d.ts.map +1 -0
- package/dist/scripts/shared/progress/progress.js +46 -0
- package/dist/scripts/shared/progress/progress.js.map +1 -0
- package/dist/scripts/shared/prompt/prompt.d.ts +38 -0
- package/dist/scripts/shared/prompt/prompt.d.ts.map +1 -0
- package/dist/scripts/shared/prompt/prompt.js +77 -0
- package/dist/scripts/shared/prompt/prompt.js.map +1 -0
- package/dist/types/board.d.ts +13 -0
- package/dist/types/board.d.ts.map +1 -0
- package/dist/types/board.js +5 -0
- package/dist/types/board.js.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/ansi/ansi.d.ts +55 -0
- package/dist/utils/ansi/ansi.d.ts.map +1 -0
- package/dist/utils/ansi/ansi.js +55 -0
- package/dist/utils/ansi/ansi.js.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/parse-json/parse-json.d.ts +20 -0
- package/dist/utils/parse-json/parse-json.d.ts.map +1 -0
- package/dist/utils/parse-json/parse-json.js +27 -0
- package/dist/utils/parse-json/parse-json.js.map +1 -0
- package/hooks/clancy-check-update.js +2 -2
- package/hooks/clancy-credential-guard.js +8 -1
- package/package.json +52 -8
- package/registry/boards.json +3 -6
- package/src/templates/CLAUDE.md +1 -1
- package/src/workflows/doctor.md +32 -23
- package/src/workflows/init.md +88 -19
- package/src/workflows/logs.md +13 -6
- package/src/workflows/map-codebase.md +17 -16
- package/src/workflows/once.md +22 -12
- package/src/workflows/review.md +40 -27
- package/src/workflows/run.md +20 -12
- package/src/workflows/scaffold.md +12 -1023
- package/src/workflows/settings.md +9 -6
- package/src/workflows/status.md +17 -8
- package/src/workflows/uninstall.md +11 -6
- package/src/workflows/update.md +13 -11
- package/bin/install.js +0 -362
- package/src/templates/scripts/clancy-afk.sh +0 -111
- package/src/templates/scripts/clancy-once-github.sh +0 -249
- package/src/templates/scripts/clancy-once-linear.sh +0 -320
- package/src/templates/scripts/clancy-once.sh +0 -322
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clancy Jira board script.
|
|
3
|
+
*
|
|
4
|
+
* Fetches a ticket from Jira Cloud, creates branches, invokes Claude,
|
|
5
|
+
* squash merges, transitions status, and sends notifications.
|
|
6
|
+
*
|
|
7
|
+
* Uses the new POST `/rest/api/3/search/jql` endpoint (old GET `/search`
|
|
8
|
+
* was removed by Atlassian in August 2025).
|
|
9
|
+
*/
|
|
10
|
+
import { jiraSearchResponseSchema, jiraTransitionsResponseSchema, } from '../../../schemas/jira.js';
|
|
11
|
+
import { jiraHeaders, pingEndpoint } from '../../../scripts/shared/http/http.js';
|
|
12
|
+
const SAFE_VALUE_PATTERN = /^[a-zA-Z0-9 _\-'.]+$/;
|
|
13
|
+
const ISSUE_KEY_PATTERN = /^[A-Z][A-Z0-9]+-\d+$/;
|
|
14
|
+
/**
|
|
15
|
+
* Build Jira Basic auth header value.
|
|
16
|
+
*
|
|
17
|
+
* @param user - The Jira username (email).
|
|
18
|
+
* @param token - The Jira API token.
|
|
19
|
+
* @returns The Base64-encoded `user:token` string for Basic auth.
|
|
20
|
+
*/
|
|
21
|
+
export function buildAuthHeader(user, token) {
|
|
22
|
+
return Buffer.from(`${user}:${token}`).toString('base64');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Validate that a user-controlled value is safe for JQL injection.
|
|
26
|
+
*
|
|
27
|
+
* @param value - The value to validate.
|
|
28
|
+
* @returns `true` if the value matches the safe pattern.
|
|
29
|
+
*/
|
|
30
|
+
export function isSafeJqlValue(value) {
|
|
31
|
+
return SAFE_VALUE_PATTERN.test(value);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Ping the Jira API to verify connectivity and credentials.
|
|
35
|
+
*
|
|
36
|
+
* @param baseUrl - The Jira Cloud base URL.
|
|
37
|
+
* @param projectKey - The Jira project key.
|
|
38
|
+
* @param auth - The Base64-encoded Basic auth string.
|
|
39
|
+
* @returns An object with `ok` and optional `error` message.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* const result = await pingJira('https://example.atlassian.net', 'PROJ', authHeader);
|
|
44
|
+
* if (!result.ok) console.error(result.error);
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export async function pingJira(baseUrl, projectKey, auth) {
|
|
48
|
+
return pingEndpoint(`${baseUrl}/rest/api/3/project/${projectKey}`, jiraHeaders(auth), {
|
|
49
|
+
401: '✗ Jira auth failed — check credentials',
|
|
50
|
+
403: '✗ Jira permission denied for this project',
|
|
51
|
+
404: `✗ Jira project "${projectKey}" not found`,
|
|
52
|
+
}, '✗ Could not reach Jira — check network');
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Build the JQL query for fetching the next ticket.
|
|
56
|
+
*
|
|
57
|
+
* @param projectKey - The Jira project key.
|
|
58
|
+
* @param status - The JQL status to filter by (default: `"To Do"`).
|
|
59
|
+
* @param sprint - If set, adds an open sprint filter.
|
|
60
|
+
* @param label - If set, adds a label filter.
|
|
61
|
+
* @returns The JQL query string.
|
|
62
|
+
*/
|
|
63
|
+
export function buildJql(projectKey, status, sprint, label) {
|
|
64
|
+
const parts = [`project="${projectKey}"`];
|
|
65
|
+
if (sprint)
|
|
66
|
+
parts.push('sprint in openSprints()');
|
|
67
|
+
if (label)
|
|
68
|
+
parts.push(`labels = "${label}"`);
|
|
69
|
+
parts.push(`assignee=currentUser()`);
|
|
70
|
+
parts.push(`status="${status}"`);
|
|
71
|
+
parts.push('ORDER BY priority ASC');
|
|
72
|
+
return parts.join(' AND ');
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Extract all text strings from a Jira ADF (Atlassian Document Format) description.
|
|
76
|
+
*
|
|
77
|
+
* Recursively walks the ADF tree and collects all string values.
|
|
78
|
+
*
|
|
79
|
+
* @param adf - The ADF description object (or `undefined`).
|
|
80
|
+
* @returns A single string with all text content joined by spaces.
|
|
81
|
+
*/
|
|
82
|
+
export function extractAdfText(adf) {
|
|
83
|
+
if (!adf || typeof adf !== 'object')
|
|
84
|
+
return '';
|
|
85
|
+
const strings = [];
|
|
86
|
+
function walk(node) {
|
|
87
|
+
if (typeof node === 'string') {
|
|
88
|
+
strings.push(node);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (Array.isArray(node)) {
|
|
92
|
+
for (const item of node)
|
|
93
|
+
walk(item);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (node && typeof node === 'object') {
|
|
97
|
+
for (const value of Object.values(node)) {
|
|
98
|
+
walk(value);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
walk(adf);
|
|
103
|
+
return strings.join(' ');
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Fetch the next available ticket from Jira.
|
|
107
|
+
*
|
|
108
|
+
* @param baseUrl - The Jira Cloud base URL.
|
|
109
|
+
* @param auth - The Base64-encoded Basic auth string.
|
|
110
|
+
* @param projectKey - The Jira project key.
|
|
111
|
+
* @param status - The JQL status to filter by.
|
|
112
|
+
* @param sprint - Optional sprint filter.
|
|
113
|
+
* @param label - Optional label filter.
|
|
114
|
+
* @returns The fetched ticket, or `undefined` if no tickets are available.
|
|
115
|
+
*/
|
|
116
|
+
export async function fetchTicket(baseUrl, auth, projectKey, status, sprint, label) {
|
|
117
|
+
const jql = buildJql(projectKey, status, sprint, label);
|
|
118
|
+
let response;
|
|
119
|
+
try {
|
|
120
|
+
response = await fetch(`${baseUrl}/rest/api/3/search/jql`, {
|
|
121
|
+
method: 'POST',
|
|
122
|
+
headers: {
|
|
123
|
+
...jiraHeaders(auth),
|
|
124
|
+
'Content-Type': 'application/json',
|
|
125
|
+
},
|
|
126
|
+
body: JSON.stringify({
|
|
127
|
+
jql,
|
|
128
|
+
maxResults: 1,
|
|
129
|
+
fields: [
|
|
130
|
+
'summary',
|
|
131
|
+
'description',
|
|
132
|
+
'issuelinks',
|
|
133
|
+
'parent',
|
|
134
|
+
'customfield_10014',
|
|
135
|
+
],
|
|
136
|
+
}),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
console.warn(`⚠ Jira API request failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
if (!response.ok) {
|
|
144
|
+
console.warn(`⚠ Jira API returned HTTP ${response.status}`);
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
let json;
|
|
148
|
+
try {
|
|
149
|
+
json = await response.json();
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
console.warn('⚠ Jira API returned invalid JSON');
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
const parsed = jiraSearchResponseSchema.safeParse(json);
|
|
156
|
+
if (!parsed.success) {
|
|
157
|
+
console.warn(`⚠ Unexpected Jira response shape: ${parsed.error.message}`);
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
if (!parsed.data.issues.length)
|
|
161
|
+
return undefined;
|
|
162
|
+
const issue = parsed.data.issues[0];
|
|
163
|
+
const fields = issue.fields;
|
|
164
|
+
// Extract blockers
|
|
165
|
+
const blockers = (fields.issuelinks ?? [])
|
|
166
|
+
.filter((link) => link.type?.name === 'Blocks' && link.inwardIssue?.key)
|
|
167
|
+
.map((link) => link.inwardIssue?.key)
|
|
168
|
+
.filter((key) => Boolean(key));
|
|
169
|
+
// Extract epic (next-gen parent OR classic customfield)
|
|
170
|
+
const epicKey = fields.parent?.key ?? fields.customfield_10014 ?? undefined;
|
|
171
|
+
return {
|
|
172
|
+
key: issue.key,
|
|
173
|
+
title: fields.summary,
|
|
174
|
+
description: extractAdfText(fields.description),
|
|
175
|
+
provider: 'jira',
|
|
176
|
+
epicKey,
|
|
177
|
+
blockers,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Look up a Jira transition ID by status name.
|
|
182
|
+
*
|
|
183
|
+
* @param baseUrl - The Jira Cloud base URL.
|
|
184
|
+
* @param auth - The Base64-encoded Basic auth string.
|
|
185
|
+
* @param issueKey - The Jira issue key (e.g., `'PROJ-123'`).
|
|
186
|
+
* @param statusName - The target status name (e.g., `'In Progress'`).
|
|
187
|
+
* @returns The transition ID, or `undefined` if not found.
|
|
188
|
+
*/
|
|
189
|
+
export async function lookupTransitionId(baseUrl, auth, issueKey, statusName) {
|
|
190
|
+
if (!ISSUE_KEY_PATTERN.test(issueKey))
|
|
191
|
+
return undefined;
|
|
192
|
+
let response;
|
|
193
|
+
try {
|
|
194
|
+
response = await fetch(`${baseUrl}/rest/api/3/issue/${issueKey}/transitions`, { headers: jiraHeaders(auth) });
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
console.warn(`⚠ Jira transitions request failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
if (!response.ok)
|
|
201
|
+
return undefined;
|
|
202
|
+
let json;
|
|
203
|
+
try {
|
|
204
|
+
json = await response.json();
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
console.warn('⚠ Jira transitions returned invalid JSON');
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
const parsed = jiraTransitionsResponseSchema.safeParse(json);
|
|
211
|
+
if (!parsed.success) {
|
|
212
|
+
console.warn(`⚠ Unexpected Jira transitions response: ${parsed.error.message}`);
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
const transition = parsed.data.transitions.find((t) => t.name === statusName);
|
|
216
|
+
return transition?.id;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Transition a Jira issue to a new status.
|
|
220
|
+
*
|
|
221
|
+
* Fetches available transitions and executes the one matching the target status name.
|
|
222
|
+
* Best-effort — never throws on failure.
|
|
223
|
+
*
|
|
224
|
+
* @param baseUrl - The Jira Cloud base URL.
|
|
225
|
+
* @param auth - The Base64-encoded Basic auth string.
|
|
226
|
+
* @param issueKey - The Jira issue key (e.g., `'PROJ-123'`).
|
|
227
|
+
* @param statusName - The target status name (e.g., `'In Progress'`).
|
|
228
|
+
* @returns `true` if the transition succeeded.
|
|
229
|
+
*/
|
|
230
|
+
export async function transitionIssue(baseUrl, auth, issueKey, statusName) {
|
|
231
|
+
try {
|
|
232
|
+
const transitionId = await lookupTransitionId(baseUrl, auth, issueKey, statusName);
|
|
233
|
+
if (!transitionId) {
|
|
234
|
+
console.warn(`⚠ Jira transition "${statusName}" not found for ${issueKey}`);
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
const response = await fetch(`${baseUrl}/rest/api/3/issue/${issueKey}/transitions`, {
|
|
238
|
+
method: 'POST',
|
|
239
|
+
headers: {
|
|
240
|
+
...jiraHeaders(auth),
|
|
241
|
+
'Content-Type': 'application/json',
|
|
242
|
+
},
|
|
243
|
+
body: JSON.stringify({ transition: { id: transitionId } }),
|
|
244
|
+
});
|
|
245
|
+
return response.ok;
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=jira.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jira.js","sourceRoot":"","sources":["../../../../src/scripts/board/jira/jira.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { Ticket } from '../../../types/index.js';
|
|
2
|
+
type LinearEnv = {
|
|
3
|
+
LINEAR_API_KEY: string;
|
|
4
|
+
LINEAR_TEAM_ID: string;
|
|
5
|
+
CLANCY_LABEL?: string;
|
|
6
|
+
CLANCY_BASE_BRANCH?: string;
|
|
7
|
+
CLANCY_STATUS_IN_PROGRESS?: string;
|
|
8
|
+
CLANCY_STATUS_DONE?: string;
|
|
9
|
+
CLANCY_MODEL?: string;
|
|
10
|
+
CLANCY_NOTIFY_WEBHOOK?: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Validate that a team ID is safe for use in GraphQL variables.
|
|
14
|
+
*
|
|
15
|
+
* @param teamId - The Linear team ID to validate.
|
|
16
|
+
* @returns `true` if the ID matches the safe pattern.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isValidTeamId(teamId: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Make a GraphQL request to the Linear API.
|
|
21
|
+
*
|
|
22
|
+
* Personal API keys are passed directly (no "Bearer" prefix).
|
|
23
|
+
*
|
|
24
|
+
* @param apiKey - The Linear personal API key.
|
|
25
|
+
* @param query - The GraphQL query string.
|
|
26
|
+
* @param variables - The GraphQL variables object.
|
|
27
|
+
* @returns The raw JSON response, or `undefined` on failure.
|
|
28
|
+
*/
|
|
29
|
+
export declare function linearGraphql(apiKey: string, query: string, variables?: Record<string, unknown>): Promise<unknown>;
|
|
30
|
+
/**
|
|
31
|
+
* Ping the Linear API to verify connectivity and credentials.
|
|
32
|
+
*
|
|
33
|
+
* @param apiKey - The Linear personal API key.
|
|
34
|
+
* @returns An object with `ok` and optional `error` message.
|
|
35
|
+
*/
|
|
36
|
+
export declare function pingLinear(apiKey: string): Promise<{
|
|
37
|
+
ok: boolean;
|
|
38
|
+
error?: string;
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* Fetch the next available issue from Linear.
|
|
42
|
+
*
|
|
43
|
+
* Filters by `state.type: "unstarted"` (enum, works regardless of team
|
|
44
|
+
* column naming) and optionally by label.
|
|
45
|
+
*
|
|
46
|
+
* @param env - The Linear environment variables.
|
|
47
|
+
* @returns The fetched ticket with optional parent info, or `undefined` if none available.
|
|
48
|
+
*/
|
|
49
|
+
export declare function fetchIssue(env: LinearEnv): Promise<(Ticket & {
|
|
50
|
+
issueId: string;
|
|
51
|
+
parentIdentifier?: string;
|
|
52
|
+
}) | undefined>;
|
|
53
|
+
/**
|
|
54
|
+
* Look up a Linear workflow state ID by name and team.
|
|
55
|
+
*
|
|
56
|
+
* @param apiKey - The Linear personal API key.
|
|
57
|
+
* @param teamId - The Linear team ID.
|
|
58
|
+
* @param stateName - The workflow state name (e.g., `'In Progress'`).
|
|
59
|
+
* @returns The state ID, or `undefined` if not found.
|
|
60
|
+
*/
|
|
61
|
+
export declare function lookupWorkflowStateId(apiKey: string, teamId: string, stateName: string): Promise<string | undefined>;
|
|
62
|
+
/**
|
|
63
|
+
* Execute a state transition on a Linear issue.
|
|
64
|
+
*
|
|
65
|
+
* @param apiKey - The Linear personal API key.
|
|
66
|
+
* @param issueId - The Linear issue internal ID.
|
|
67
|
+
* @param stateId - The target workflow state ID.
|
|
68
|
+
* @returns `true` if the mutation succeeded.
|
|
69
|
+
*/
|
|
70
|
+
export declare function executeStateTransition(apiKey: string, issueId: string, stateId: string): Promise<boolean>;
|
|
71
|
+
/**
|
|
72
|
+
* Transition a Linear issue to a new workflow state.
|
|
73
|
+
*
|
|
74
|
+
* Looks up the workflow state ID by name, then executes the `issueUpdate` mutation.
|
|
75
|
+
* Best-effort — never throws on failure.
|
|
76
|
+
*
|
|
77
|
+
* @param apiKey - The Linear personal API key.
|
|
78
|
+
* @param teamId - The Linear team ID.
|
|
79
|
+
* @param issueId - The Linear issue internal ID.
|
|
80
|
+
* @param stateName - The target workflow state name (e.g., `'In Progress'`).
|
|
81
|
+
* @returns `true` if the transition succeeded.
|
|
82
|
+
*/
|
|
83
|
+
export declare function transitionIssue(apiKey: string, teamId: string, issueId: string, stateName: string): Promise<boolean>;
|
|
84
|
+
export {};
|
|
85
|
+
//# sourceMappingURL=linear.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linear.d.ts","sourceRoot":"","sources":["../../../../src/scripts/board/linear/linear.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAK/C,KAAK,SAAS,GAAG;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAErD;AAED;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,OAAO,CAAC,OAAO,CAAC,CA8BlB;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAO1C;AAED;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CACrD,CAAC,MAAM,GAAG;IACR,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC,GACF,SAAS,CACZ,CA0DA;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAuB7B;AAED;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,OAAO,CAAC,CAoBlB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAelB"}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clancy Linear board script.
|
|
3
|
+
*
|
|
4
|
+
* Fetches an issue from Linear's GraphQL API, creates branches,
|
|
5
|
+
* invokes Claude, squash merges, transitions status, and sends notifications.
|
|
6
|
+
*
|
|
7
|
+
* Important: Linear personal API keys do NOT use "Bearer" prefix.
|
|
8
|
+
* Only OAuth tokens use "Bearer". This is intentional per Linear docs.
|
|
9
|
+
*/
|
|
10
|
+
import { linearIssueUpdateResponseSchema, linearIssuesResponseSchema, linearViewerResponseSchema, linearWorkflowStatesResponseSchema, } from '../../../schemas/linear.js';
|
|
11
|
+
const SAFE_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
12
|
+
const LINEAR_API_URL = 'https://api.linear.app/graphql';
|
|
13
|
+
/**
|
|
14
|
+
* Validate that a team ID is safe for use in GraphQL variables.
|
|
15
|
+
*
|
|
16
|
+
* @param teamId - The Linear team ID to validate.
|
|
17
|
+
* @returns `true` if the ID matches the safe pattern.
|
|
18
|
+
*/
|
|
19
|
+
export function isValidTeamId(teamId) {
|
|
20
|
+
return SAFE_ID_PATTERN.test(teamId);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Make a GraphQL request to the Linear API.
|
|
24
|
+
*
|
|
25
|
+
* Personal API keys are passed directly (no "Bearer" prefix).
|
|
26
|
+
*
|
|
27
|
+
* @param apiKey - The Linear personal API key.
|
|
28
|
+
* @param query - The GraphQL query string.
|
|
29
|
+
* @param variables - The GraphQL variables object.
|
|
30
|
+
* @returns The raw JSON response, or `undefined` on failure.
|
|
31
|
+
*/
|
|
32
|
+
export async function linearGraphql(apiKey, query, variables) {
|
|
33
|
+
let response;
|
|
34
|
+
try {
|
|
35
|
+
response = await fetch(LINEAR_API_URL, {
|
|
36
|
+
method: 'POST',
|
|
37
|
+
headers: {
|
|
38
|
+
Authorization: apiKey,
|
|
39
|
+
'Content-Type': 'application/json',
|
|
40
|
+
},
|
|
41
|
+
body: JSON.stringify({ query, variables }),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
console.warn(`⚠ Linear API request failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
console.warn(`⚠ Linear API returned HTTP ${response.status}`);
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
return await response.json();
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
console.warn('⚠ Linear API returned invalid JSON');
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Ping the Linear API to verify connectivity and credentials.
|
|
62
|
+
*
|
|
63
|
+
* @param apiKey - The Linear personal API key.
|
|
64
|
+
* @returns An object with `ok` and optional `error` message.
|
|
65
|
+
*/
|
|
66
|
+
export async function pingLinear(apiKey) {
|
|
67
|
+
const raw = await linearGraphql(apiKey, '{ viewer { id } }');
|
|
68
|
+
const parsed = linearViewerResponseSchema.safeParse(raw);
|
|
69
|
+
if (parsed.success && parsed.data.data?.viewer?.id)
|
|
70
|
+
return { ok: true };
|
|
71
|
+
return { ok: false, error: '✗ Linear auth failed — check LINEAR_API_KEY' };
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Fetch the next available issue from Linear.
|
|
75
|
+
*
|
|
76
|
+
* Filters by `state.type: "unstarted"` (enum, works regardless of team
|
|
77
|
+
* column naming) and optionally by label.
|
|
78
|
+
*
|
|
79
|
+
* @param env - The Linear environment variables.
|
|
80
|
+
* @returns The fetched ticket with optional parent info, or `undefined` if none available.
|
|
81
|
+
*/
|
|
82
|
+
export async function fetchIssue(env) {
|
|
83
|
+
const label = env.CLANCY_LABEL?.trim();
|
|
84
|
+
const hasLabel = Boolean(label);
|
|
85
|
+
const labelFilter = hasLabel ? 'labels: { name: { eq: $label } }' : '';
|
|
86
|
+
const query = `
|
|
87
|
+
query($teamId: String!${hasLabel ? ', $label: String!' : ''}) {
|
|
88
|
+
viewer {
|
|
89
|
+
assignedIssues(
|
|
90
|
+
filter: {
|
|
91
|
+
state: { type: { eq: "unstarted" } }
|
|
92
|
+
team: { id: { eq: $teamId } }
|
|
93
|
+
${labelFilter}
|
|
94
|
+
}
|
|
95
|
+
first: 1
|
|
96
|
+
orderBy: priority
|
|
97
|
+
) {
|
|
98
|
+
nodes {
|
|
99
|
+
id
|
|
100
|
+
identifier
|
|
101
|
+
title
|
|
102
|
+
description
|
|
103
|
+
parent { identifier title }
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
`;
|
|
109
|
+
const variables = {
|
|
110
|
+
teamId: env.LINEAR_TEAM_ID,
|
|
111
|
+
};
|
|
112
|
+
if (hasLabel)
|
|
113
|
+
variables.label = label;
|
|
114
|
+
const raw = await linearGraphql(env.LINEAR_API_KEY, query, variables);
|
|
115
|
+
const parsed = linearIssuesResponseSchema.safeParse(raw);
|
|
116
|
+
if (!parsed.success) {
|
|
117
|
+
console.warn(`⚠ Unexpected Linear response shape: ${parsed.error.message}`);
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
const nodes = parsed.data.data?.viewer?.assignedIssues?.nodes;
|
|
121
|
+
if (!nodes?.length)
|
|
122
|
+
return undefined;
|
|
123
|
+
const issue = nodes[0];
|
|
124
|
+
return {
|
|
125
|
+
key: issue.identifier,
|
|
126
|
+
title: issue.title,
|
|
127
|
+
description: issue.description ?? '',
|
|
128
|
+
provider: 'linear',
|
|
129
|
+
issueId: issue.id,
|
|
130
|
+
parentIdentifier: issue.parent?.identifier,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Look up a Linear workflow state ID by name and team.
|
|
135
|
+
*
|
|
136
|
+
* @param apiKey - The Linear personal API key.
|
|
137
|
+
* @param teamId - The Linear team ID.
|
|
138
|
+
* @param stateName - The workflow state name (e.g., `'In Progress'`).
|
|
139
|
+
* @returns The state ID, or `undefined` if not found.
|
|
140
|
+
*/
|
|
141
|
+
export async function lookupWorkflowStateId(apiKey, teamId, stateName) {
|
|
142
|
+
const query = `
|
|
143
|
+
query($teamId: String!, $name: String!) {
|
|
144
|
+
workflowStates(filter: {
|
|
145
|
+
team: { id: { eq: $teamId } }
|
|
146
|
+
name: { eq: $name }
|
|
147
|
+
}) {
|
|
148
|
+
nodes { id }
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
`;
|
|
152
|
+
const raw = await linearGraphql(apiKey, query, { teamId, name: stateName });
|
|
153
|
+
const parsed = linearWorkflowStatesResponseSchema.safeParse(raw);
|
|
154
|
+
if (!parsed.success) {
|
|
155
|
+
console.warn(`⚠ Unexpected Linear workflowStates response: ${parsed.error.message}`);
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
return parsed.data.data?.workflowStates?.nodes?.[0]?.id;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Execute a state transition on a Linear issue.
|
|
162
|
+
*
|
|
163
|
+
* @param apiKey - The Linear personal API key.
|
|
164
|
+
* @param issueId - The Linear issue internal ID.
|
|
165
|
+
* @param stateId - The target workflow state ID.
|
|
166
|
+
* @returns `true` if the mutation succeeded.
|
|
167
|
+
*/
|
|
168
|
+
export async function executeStateTransition(apiKey, issueId, stateId) {
|
|
169
|
+
const mutation = `
|
|
170
|
+
mutation($issueId: String!, $stateId: String!) {
|
|
171
|
+
issueUpdate(id: $issueId, input: { stateId: $stateId }) {
|
|
172
|
+
success
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
`;
|
|
176
|
+
const raw = await linearGraphql(apiKey, mutation, { issueId, stateId });
|
|
177
|
+
const parsed = linearIssueUpdateResponseSchema.safeParse(raw);
|
|
178
|
+
if (!parsed.success) {
|
|
179
|
+
console.warn(`⚠ Unexpected Linear issueUpdate response: ${parsed.error.message}`);
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
return parsed.data.data?.issueUpdate?.success === true;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Transition a Linear issue to a new workflow state.
|
|
186
|
+
*
|
|
187
|
+
* Looks up the workflow state ID by name, then executes the `issueUpdate` mutation.
|
|
188
|
+
* Best-effort — never throws on failure.
|
|
189
|
+
*
|
|
190
|
+
* @param apiKey - The Linear personal API key.
|
|
191
|
+
* @param teamId - The Linear team ID.
|
|
192
|
+
* @param issueId - The Linear issue internal ID.
|
|
193
|
+
* @param stateName - The target workflow state name (e.g., `'In Progress'`).
|
|
194
|
+
* @returns `true` if the transition succeeded.
|
|
195
|
+
*/
|
|
196
|
+
export async function transitionIssue(apiKey, teamId, issueId, stateName) {
|
|
197
|
+
try {
|
|
198
|
+
const stateId = await lookupWorkflowStateId(apiKey, teamId, stateName);
|
|
199
|
+
if (!stateId) {
|
|
200
|
+
console.warn(`⚠ Linear workflow state "${stateName}" not found — check team configuration`);
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
return await executeStateTransition(apiKey, issueId, stateId);
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=linear.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linear.js","sourceRoot":"","sources":["../../../../src/scripts/board/linear/linear.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run the once orchestrator — full ticket lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* @param argv - Process arguments (supports `--dry-run` flag).
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* await run(process.argv);
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export declare function run(argv: string[]): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=once.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"once.d.ts","sourceRoot":"","sources":["../../../src/scripts/once/once.ts"],"names":[],"mappings":"AAoPA;;;;;;;;;GASG;AACH,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA0NvD"}
|