@rotorsoft/gent 1.0.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 +397 -0
- package/dist/chunk-NRTQPDZB.js +667 -0
- package/dist/chunk-NRTQPDZB.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1184 -0
- package/dist/index.js.map +1 -0
- package/dist/setup-labels-2YOKZ2AK.js +8 -0
- package/dist/setup-labels-2YOKZ2AK.js.map +1 -0
- package/package.json +87 -0
- package/templates/.gent.yml +61 -0
- package/templates/AGENT.md +108 -0
- package/templates/issue-template.md +44 -0
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/utils/logger.ts
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
var logger = {
|
|
6
|
+
info: (message) => console.log(chalk.blue("\u2139"), message),
|
|
7
|
+
success: (message) => console.log(chalk.green("\u2713"), message),
|
|
8
|
+
warning: (message) => console.log(chalk.yellow("\u26A0"), message),
|
|
9
|
+
error: (message) => console.log(chalk.red("\u2717"), message),
|
|
10
|
+
debug: (message) => {
|
|
11
|
+
if (process.env.DEBUG) {
|
|
12
|
+
console.log(chalk.gray("\u22EF"), message);
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
dim: (message) => console.log(chalk.dim(message)),
|
|
16
|
+
bold: (message) => console.log(chalk.bold(message)),
|
|
17
|
+
highlight: (message) => console.log(chalk.cyan(message)),
|
|
18
|
+
box: (title, content) => {
|
|
19
|
+
const lines = content.split("\n");
|
|
20
|
+
const maxLen = Math.max(title.length, ...lines.map((l) => l.length)) + 4;
|
|
21
|
+
const border = "\u2500".repeat(maxLen);
|
|
22
|
+
console.log(chalk.dim(`\u250C${border}\u2510`));
|
|
23
|
+
console.log(chalk.dim("\u2502"), chalk.bold(title.padEnd(maxLen - 2)), chalk.dim("\u2502"));
|
|
24
|
+
console.log(chalk.dim(`\u251C${border}\u2524`));
|
|
25
|
+
for (const line of lines) {
|
|
26
|
+
console.log(chalk.dim("\u2502"), line.padEnd(maxLen - 2), chalk.dim("\u2502"));
|
|
27
|
+
}
|
|
28
|
+
console.log(chalk.dim(`\u2514${border}\u2518`));
|
|
29
|
+
},
|
|
30
|
+
list: (items, bullet = "\u2022") => {
|
|
31
|
+
for (const item of items) {
|
|
32
|
+
console.log(chalk.dim(bullet), item);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
newline: () => console.log()
|
|
36
|
+
};
|
|
37
|
+
var colors = {
|
|
38
|
+
issue: chalk.cyan,
|
|
39
|
+
branch: chalk.magenta,
|
|
40
|
+
label: chalk.yellow,
|
|
41
|
+
file: chalk.green,
|
|
42
|
+
command: chalk.blue,
|
|
43
|
+
url: chalk.underline.blue
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// src/utils/spinner.ts
|
|
47
|
+
import ora from "ora";
|
|
48
|
+
function createSpinner(text) {
|
|
49
|
+
return ora({
|
|
50
|
+
text,
|
|
51
|
+
spinner: "dots"
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async function withSpinner(text, fn) {
|
|
55
|
+
const spinner = createSpinner(text);
|
|
56
|
+
spinner.start();
|
|
57
|
+
try {
|
|
58
|
+
const result = await fn();
|
|
59
|
+
spinner.succeed();
|
|
60
|
+
return result;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
spinner.fail();
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/lib/config.ts
|
|
68
|
+
import { existsSync, readFileSync } from "fs";
|
|
69
|
+
import { join } from "path";
|
|
70
|
+
import { parse as parseYaml } from "yaml";
|
|
71
|
+
var DEFAULT_CONFIG = {
|
|
72
|
+
version: 1,
|
|
73
|
+
github: {
|
|
74
|
+
labels: {
|
|
75
|
+
workflow: {
|
|
76
|
+
ready: "ai-ready",
|
|
77
|
+
in_progress: "ai-in-progress",
|
|
78
|
+
completed: "ai-completed",
|
|
79
|
+
blocked: "ai-blocked"
|
|
80
|
+
},
|
|
81
|
+
types: ["feature", "fix", "refactor", "chore", "docs", "test"],
|
|
82
|
+
priorities: ["critical", "high", "medium", "low"],
|
|
83
|
+
risks: ["low", "medium", "high"],
|
|
84
|
+
areas: ["ui", "api", "database", "workers", "shared", "testing", "infra"]
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
branch: {
|
|
88
|
+
pattern: "{author}/{type}-{issue}-{slug}",
|
|
89
|
+
author_source: "git",
|
|
90
|
+
author_env_var: "GENT_AUTHOR"
|
|
91
|
+
},
|
|
92
|
+
progress: {
|
|
93
|
+
file: "progress.txt",
|
|
94
|
+
archive_threshold: 500,
|
|
95
|
+
archive_dir: ".gent/archive"
|
|
96
|
+
},
|
|
97
|
+
claude: {
|
|
98
|
+
permission_mode: "acceptEdits",
|
|
99
|
+
agent_file: "AGENT.md"
|
|
100
|
+
},
|
|
101
|
+
validation: ["npm run typecheck", "npm run lint", "npm run test"]
|
|
102
|
+
};
|
|
103
|
+
function getConfigPath(cwd = process.cwd()) {
|
|
104
|
+
return join(cwd, ".gent.yml");
|
|
105
|
+
}
|
|
106
|
+
function getAgentPath(cwd = process.cwd()) {
|
|
107
|
+
const config = loadConfig(cwd);
|
|
108
|
+
const agentPath = join(cwd, config.claude.agent_file);
|
|
109
|
+
return existsSync(agentPath) ? agentPath : null;
|
|
110
|
+
}
|
|
111
|
+
function loadConfig(cwd = process.cwd()) {
|
|
112
|
+
const configPath = getConfigPath(cwd);
|
|
113
|
+
if (!existsSync(configPath)) {
|
|
114
|
+
return DEFAULT_CONFIG;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
const content = readFileSync(configPath, "utf-8");
|
|
118
|
+
const userConfig = parseYaml(content);
|
|
119
|
+
return mergeConfig(DEFAULT_CONFIG, userConfig);
|
|
120
|
+
} catch {
|
|
121
|
+
return DEFAULT_CONFIG;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function loadAgentInstructions(cwd = process.cwd()) {
|
|
125
|
+
const agentPath = getAgentPath(cwd);
|
|
126
|
+
if (!agentPath) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
return readFileSync(agentPath, "utf-8");
|
|
131
|
+
} catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function configExists(cwd = process.cwd()) {
|
|
136
|
+
return existsSync(getConfigPath(cwd));
|
|
137
|
+
}
|
|
138
|
+
function mergeConfig(defaults, user) {
|
|
139
|
+
return {
|
|
140
|
+
version: user.version ?? defaults.version,
|
|
141
|
+
github: {
|
|
142
|
+
labels: {
|
|
143
|
+
workflow: {
|
|
144
|
+
...defaults.github.labels.workflow,
|
|
145
|
+
...user.github?.labels?.workflow
|
|
146
|
+
},
|
|
147
|
+
types: user.github?.labels?.types ?? defaults.github.labels.types,
|
|
148
|
+
priorities: user.github?.labels?.priorities ?? defaults.github.labels.priorities,
|
|
149
|
+
risks: user.github?.labels?.risks ?? defaults.github.labels.risks,
|
|
150
|
+
areas: user.github?.labels?.areas ?? defaults.github.labels.areas
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
branch: {
|
|
154
|
+
...defaults.branch,
|
|
155
|
+
...user.branch
|
|
156
|
+
},
|
|
157
|
+
progress: {
|
|
158
|
+
...defaults.progress,
|
|
159
|
+
...user.progress
|
|
160
|
+
},
|
|
161
|
+
claude: {
|
|
162
|
+
...defaults.claude,
|
|
163
|
+
...user.claude
|
|
164
|
+
},
|
|
165
|
+
validation: user.validation ?? defaults.validation
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function generateDefaultConfig() {
|
|
169
|
+
return `# Gent Configuration
|
|
170
|
+
# See https://github.com/rotorsoft/gent for documentation
|
|
171
|
+
version: 1
|
|
172
|
+
|
|
173
|
+
# GitHub settings
|
|
174
|
+
github:
|
|
175
|
+
labels:
|
|
176
|
+
workflow:
|
|
177
|
+
ready: "ai-ready"
|
|
178
|
+
in_progress: "ai-in-progress"
|
|
179
|
+
completed: "ai-completed"
|
|
180
|
+
blocked: "ai-blocked"
|
|
181
|
+
types:
|
|
182
|
+
- feature
|
|
183
|
+
- fix
|
|
184
|
+
- refactor
|
|
185
|
+
- chore
|
|
186
|
+
- docs
|
|
187
|
+
- test
|
|
188
|
+
priorities:
|
|
189
|
+
- critical
|
|
190
|
+
- high
|
|
191
|
+
- medium
|
|
192
|
+
- low
|
|
193
|
+
risks:
|
|
194
|
+
- low
|
|
195
|
+
- medium
|
|
196
|
+
- high
|
|
197
|
+
areas:
|
|
198
|
+
- ui
|
|
199
|
+
- api
|
|
200
|
+
- database
|
|
201
|
+
- workers
|
|
202
|
+
- shared
|
|
203
|
+
- testing
|
|
204
|
+
- infra
|
|
205
|
+
|
|
206
|
+
# Branch naming convention
|
|
207
|
+
branch:
|
|
208
|
+
pattern: "{author}/{type}-{issue}-{slug}"
|
|
209
|
+
author_source: "git" # git | env | prompt
|
|
210
|
+
author_env_var: "GENT_AUTHOR"
|
|
211
|
+
|
|
212
|
+
# Progress tracking
|
|
213
|
+
progress:
|
|
214
|
+
file: "progress.txt"
|
|
215
|
+
archive_threshold: 500
|
|
216
|
+
archive_dir: ".gent/archive"
|
|
217
|
+
|
|
218
|
+
# Claude settings
|
|
219
|
+
claude:
|
|
220
|
+
permission_mode: "acceptEdits"
|
|
221
|
+
agent_file: "AGENT.md"
|
|
222
|
+
|
|
223
|
+
# Validation commands (run before commit)
|
|
224
|
+
validation:
|
|
225
|
+
- "npm run typecheck"
|
|
226
|
+
- "npm run lint"
|
|
227
|
+
- "npm run test"
|
|
228
|
+
`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/types/index.ts
|
|
232
|
+
var DEFAULT_LABELS = {
|
|
233
|
+
workflow: [
|
|
234
|
+
{
|
|
235
|
+
name: "ai-ready",
|
|
236
|
+
color: "0E8A16",
|
|
237
|
+
description: "Issue ready for AI implementation"
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: "ai-in-progress",
|
|
241
|
+
color: "FFA500",
|
|
242
|
+
description: "AI currently working on this"
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: "ai-completed",
|
|
246
|
+
color: "1D76DB",
|
|
247
|
+
description: "AI done, needs human review"
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: "ai-blocked",
|
|
251
|
+
color: "D93F0B",
|
|
252
|
+
description: "AI couldn't complete, needs help"
|
|
253
|
+
}
|
|
254
|
+
],
|
|
255
|
+
priority: [
|
|
256
|
+
{
|
|
257
|
+
name: "priority:critical",
|
|
258
|
+
color: "B60205",
|
|
259
|
+
description: "Blocking production"
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: "priority:high",
|
|
263
|
+
color: "D93F0B",
|
|
264
|
+
description: "Important features/bugs"
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
name: "priority:medium",
|
|
268
|
+
color: "FBCA04",
|
|
269
|
+
description: "Nice-to-have improvements"
|
|
270
|
+
},
|
|
271
|
+
{ name: "priority:low", color: "0E8A16", description: "Minor tweaks" }
|
|
272
|
+
],
|
|
273
|
+
risk: [
|
|
274
|
+
{
|
|
275
|
+
name: "risk:low",
|
|
276
|
+
color: "C2E0C6",
|
|
277
|
+
description: "UI changes, tests, non-critical"
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
name: "risk:medium",
|
|
281
|
+
color: "FEF2C0",
|
|
282
|
+
description: "API changes, new features"
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
name: "risk:high",
|
|
286
|
+
color: "F9D0C4",
|
|
287
|
+
description: "Migrations, auth, security"
|
|
288
|
+
}
|
|
289
|
+
],
|
|
290
|
+
type: [
|
|
291
|
+
{ name: "type:feature", color: "1D76DB", description: "New feature" },
|
|
292
|
+
{ name: "type:fix", color: "D73A4A", description: "Bug fix" },
|
|
293
|
+
{
|
|
294
|
+
name: "type:refactor",
|
|
295
|
+
color: "5319E7",
|
|
296
|
+
description: "Code improvement"
|
|
297
|
+
},
|
|
298
|
+
{ name: "type:chore", color: "FEF2C0", description: "Maintenance" },
|
|
299
|
+
{ name: "type:docs", color: "0075CA", description: "Documentation" },
|
|
300
|
+
{ name: "type:test", color: "D4C5F9", description: "Testing" }
|
|
301
|
+
],
|
|
302
|
+
area: [
|
|
303
|
+
{ name: "area:ui", color: "C5DEF5", description: "User interface" },
|
|
304
|
+
{ name: "area:api", color: "D4C5F9", description: "API/Backend" },
|
|
305
|
+
{ name: "area:database", color: "FEF2C0", description: "Database/Models" },
|
|
306
|
+
{
|
|
307
|
+
name: "area:workers",
|
|
308
|
+
color: "F9D0C4",
|
|
309
|
+
description: "Background workers"
|
|
310
|
+
},
|
|
311
|
+
{ name: "area:shared", color: "C2E0C6", description: "Shared libraries" },
|
|
312
|
+
{ name: "area:testing", color: "E99695", description: "Test infrastructure" },
|
|
313
|
+
{ name: "area:infra", color: "BFD4F2", description: "Infrastructure/DevOps" }
|
|
314
|
+
]
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// src/lib/labels.ts
|
|
318
|
+
function getAllLabels(config) {
|
|
319
|
+
const labels = [];
|
|
320
|
+
labels.push(...DEFAULT_LABELS.workflow);
|
|
321
|
+
for (const priority of config.github.labels.priorities) {
|
|
322
|
+
const defaultLabel = DEFAULT_LABELS.priority.find(
|
|
323
|
+
(l) => l.name === `priority:${priority}`
|
|
324
|
+
);
|
|
325
|
+
if (defaultLabel) {
|
|
326
|
+
labels.push(defaultLabel);
|
|
327
|
+
} else {
|
|
328
|
+
labels.push({
|
|
329
|
+
name: `priority:${priority}`,
|
|
330
|
+
color: "FBCA04",
|
|
331
|
+
description: `Priority: ${priority}`
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
for (const risk of config.github.labels.risks) {
|
|
336
|
+
const defaultLabel = DEFAULT_LABELS.risk.find(
|
|
337
|
+
(l) => l.name === `risk:${risk}`
|
|
338
|
+
);
|
|
339
|
+
if (defaultLabel) {
|
|
340
|
+
labels.push(defaultLabel);
|
|
341
|
+
} else {
|
|
342
|
+
labels.push({
|
|
343
|
+
name: `risk:${risk}`,
|
|
344
|
+
color: "FEF2C0",
|
|
345
|
+
description: `Risk: ${risk}`
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
for (const type of config.github.labels.types) {
|
|
350
|
+
const defaultLabel = DEFAULT_LABELS.type.find(
|
|
351
|
+
(l) => l.name === `type:${type}`
|
|
352
|
+
);
|
|
353
|
+
if (defaultLabel) {
|
|
354
|
+
labels.push(defaultLabel);
|
|
355
|
+
} else {
|
|
356
|
+
labels.push({
|
|
357
|
+
name: `type:${type}`,
|
|
358
|
+
color: "1D76DB",
|
|
359
|
+
description: `Type: ${type}`
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
for (const area of config.github.labels.areas) {
|
|
364
|
+
const defaultLabel = DEFAULT_LABELS.area.find(
|
|
365
|
+
(l) => l.name === `area:${area}`
|
|
366
|
+
);
|
|
367
|
+
if (defaultLabel) {
|
|
368
|
+
labels.push(defaultLabel);
|
|
369
|
+
} else {
|
|
370
|
+
labels.push({
|
|
371
|
+
name: `area:${area}`,
|
|
372
|
+
color: "C5DEF5",
|
|
373
|
+
description: `Area: ${area}`
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return labels;
|
|
378
|
+
}
|
|
379
|
+
function getWorkflowLabels(config) {
|
|
380
|
+
return {
|
|
381
|
+
ready: config.github.labels.workflow.ready,
|
|
382
|
+
inProgress: config.github.labels.workflow.in_progress,
|
|
383
|
+
completed: config.github.labels.workflow.completed,
|
|
384
|
+
blocked: config.github.labels.workflow.blocked
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
function buildIssueLabels(meta) {
|
|
388
|
+
return [
|
|
389
|
+
"ai-ready",
|
|
390
|
+
`type:${meta.type}`,
|
|
391
|
+
`priority:${meta.priority}`,
|
|
392
|
+
`risk:${meta.risk}`,
|
|
393
|
+
`area:${meta.area}`
|
|
394
|
+
];
|
|
395
|
+
}
|
|
396
|
+
function extractTypeFromLabels(labels) {
|
|
397
|
+
for (const label of labels) {
|
|
398
|
+
if (label.startsWith("type:")) {
|
|
399
|
+
return label.replace("type:", "");
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return "feature";
|
|
403
|
+
}
|
|
404
|
+
function extractPriorityFromLabels(labels) {
|
|
405
|
+
for (const label of labels) {
|
|
406
|
+
if (label.startsWith("priority:")) {
|
|
407
|
+
return label.replace("priority:", "");
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return "medium";
|
|
411
|
+
}
|
|
412
|
+
function sortByPriority(issues) {
|
|
413
|
+
const priorityOrder = ["critical", "high", "medium", "low"];
|
|
414
|
+
issues.sort((a, b) => {
|
|
415
|
+
const aPriority = extractPriorityFromLabels(a.labels);
|
|
416
|
+
const bPriority = extractPriorityFromLabels(b.labels);
|
|
417
|
+
return priorityOrder.indexOf(aPriority) - priorityOrder.indexOf(bPriority);
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// src/lib/github.ts
|
|
422
|
+
import { execa } from "execa";
|
|
423
|
+
async function getIssue(issueNumber) {
|
|
424
|
+
const { stdout } = await execa("gh", [
|
|
425
|
+
"issue",
|
|
426
|
+
"view",
|
|
427
|
+
String(issueNumber),
|
|
428
|
+
"--json",
|
|
429
|
+
"number,title,body,labels,state,assignees,url"
|
|
430
|
+
]);
|
|
431
|
+
const data = JSON.parse(stdout);
|
|
432
|
+
return {
|
|
433
|
+
number: data.number,
|
|
434
|
+
title: data.title,
|
|
435
|
+
body: data.body || "",
|
|
436
|
+
labels: data.labels.map((l) => l.name),
|
|
437
|
+
state: data.state.toLowerCase(),
|
|
438
|
+
assignee: data.assignees?.[0]?.login,
|
|
439
|
+
url: data.url
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
async function listIssues(options) {
|
|
443
|
+
const args = ["issue", "list", "--json", "number,title,body,labels,state,url"];
|
|
444
|
+
if (options.labels?.length) {
|
|
445
|
+
args.push("--label", options.labels.join(","));
|
|
446
|
+
}
|
|
447
|
+
if (options.state) {
|
|
448
|
+
args.push("--state", options.state);
|
|
449
|
+
}
|
|
450
|
+
args.push("--limit", String(options.limit || 50));
|
|
451
|
+
const { stdout } = await execa("gh", args);
|
|
452
|
+
const data = JSON.parse(stdout);
|
|
453
|
+
return data.map(
|
|
454
|
+
(d) => ({
|
|
455
|
+
number: d.number,
|
|
456
|
+
title: d.title,
|
|
457
|
+
body: d.body || "",
|
|
458
|
+
labels: d.labels.map((l) => l.name),
|
|
459
|
+
state: d.state.toLowerCase(),
|
|
460
|
+
url: d.url
|
|
461
|
+
})
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
async function createIssue(options) {
|
|
465
|
+
const args = ["issue", "create", "--title", options.title, "--body", options.body];
|
|
466
|
+
if (options.labels?.length) {
|
|
467
|
+
args.push("--label", options.labels.join(","));
|
|
468
|
+
}
|
|
469
|
+
const { stdout } = await execa("gh", args);
|
|
470
|
+
const match = stdout.match(/\/issues\/(\d+)/);
|
|
471
|
+
if (!match) {
|
|
472
|
+
throw new Error("Failed to extract issue number from gh output");
|
|
473
|
+
}
|
|
474
|
+
return parseInt(match[1], 10);
|
|
475
|
+
}
|
|
476
|
+
async function updateIssueLabels(issueNumber, options) {
|
|
477
|
+
const promises = [];
|
|
478
|
+
if (options.add?.length) {
|
|
479
|
+
promises.push(
|
|
480
|
+
execa("gh", [
|
|
481
|
+
"issue",
|
|
482
|
+
"edit",
|
|
483
|
+
String(issueNumber),
|
|
484
|
+
"--add-label",
|
|
485
|
+
options.add.join(",")
|
|
486
|
+
])
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
if (options.remove?.length) {
|
|
490
|
+
promises.push(
|
|
491
|
+
execa("gh", [
|
|
492
|
+
"issue",
|
|
493
|
+
"edit",
|
|
494
|
+
String(issueNumber),
|
|
495
|
+
"--remove-label",
|
|
496
|
+
options.remove.join(",")
|
|
497
|
+
])
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
await Promise.all(promises);
|
|
501
|
+
}
|
|
502
|
+
async function addIssueComment(issueNumber, body) {
|
|
503
|
+
await execa("gh", ["issue", "comment", String(issueNumber), "--body", body]);
|
|
504
|
+
}
|
|
505
|
+
async function assignIssue(issueNumber, assignee) {
|
|
506
|
+
await execa("gh", [
|
|
507
|
+
"issue",
|
|
508
|
+
"edit",
|
|
509
|
+
String(issueNumber),
|
|
510
|
+
"--add-assignee",
|
|
511
|
+
assignee
|
|
512
|
+
]);
|
|
513
|
+
}
|
|
514
|
+
async function createLabel(label) {
|
|
515
|
+
try {
|
|
516
|
+
await execa("gh", [
|
|
517
|
+
"label",
|
|
518
|
+
"create",
|
|
519
|
+
label.name,
|
|
520
|
+
"--color",
|
|
521
|
+
label.color,
|
|
522
|
+
"--description",
|
|
523
|
+
label.description || "",
|
|
524
|
+
"--force"
|
|
525
|
+
]);
|
|
526
|
+
} catch {
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
async function createPullRequest(options) {
|
|
530
|
+
const args = [
|
|
531
|
+
"pr",
|
|
532
|
+
"create",
|
|
533
|
+
"--title",
|
|
534
|
+
options.title,
|
|
535
|
+
"--body",
|
|
536
|
+
options.body,
|
|
537
|
+
"--assignee",
|
|
538
|
+
"@me"
|
|
539
|
+
];
|
|
540
|
+
if (options.base) {
|
|
541
|
+
args.push("--base", options.base);
|
|
542
|
+
}
|
|
543
|
+
if (options.draft) {
|
|
544
|
+
args.push("--draft");
|
|
545
|
+
}
|
|
546
|
+
const { stdout } = await execa("gh", args);
|
|
547
|
+
return stdout.trim();
|
|
548
|
+
}
|
|
549
|
+
async function getPrForBranch() {
|
|
550
|
+
try {
|
|
551
|
+
const { stdout } = await execa("gh", [
|
|
552
|
+
"pr",
|
|
553
|
+
"view",
|
|
554
|
+
"--json",
|
|
555
|
+
"number,url"
|
|
556
|
+
]);
|
|
557
|
+
const data = JSON.parse(stdout);
|
|
558
|
+
return { number: data.number, url: data.url };
|
|
559
|
+
} catch {
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
async function getCurrentUser() {
|
|
564
|
+
const { stdout } = await execa("gh", ["api", "user", "--jq", ".login"]);
|
|
565
|
+
return stdout.trim();
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// src/utils/validators.ts
|
|
569
|
+
import { execa as execa2 } from "execa";
|
|
570
|
+
async function checkGhAuth() {
|
|
571
|
+
try {
|
|
572
|
+
await execa2("gh", ["auth", "status"]);
|
|
573
|
+
return true;
|
|
574
|
+
} catch {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
async function checkClaudeCli() {
|
|
579
|
+
try {
|
|
580
|
+
await execa2("claude", ["--version"]);
|
|
581
|
+
return true;
|
|
582
|
+
} catch {
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
async function checkGitRepo() {
|
|
587
|
+
try {
|
|
588
|
+
await execa2("git", ["rev-parse", "--git-dir"]);
|
|
589
|
+
return true;
|
|
590
|
+
} catch {
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
function isValidIssueNumber(value) {
|
|
595
|
+
const num = parseInt(value, 10);
|
|
596
|
+
return !isNaN(num) && num > 0;
|
|
597
|
+
}
|
|
598
|
+
function sanitizeSlug(title, maxLength = 40) {
|
|
599
|
+
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, maxLength);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// src/commands/setup-labels.ts
|
|
603
|
+
async function setupLabelsCommand() {
|
|
604
|
+
logger.bold("Setting up GitHub labels...");
|
|
605
|
+
logger.newline();
|
|
606
|
+
const isAuthed = await checkGhAuth();
|
|
607
|
+
if (!isAuthed) {
|
|
608
|
+
logger.error("Not authenticated with GitHub. Run 'gh auth login' first.");
|
|
609
|
+
process.exit(1);
|
|
610
|
+
}
|
|
611
|
+
const config = loadConfig();
|
|
612
|
+
const labels = getAllLabels(config);
|
|
613
|
+
logger.info(`Creating ${labels.length} labels...`);
|
|
614
|
+
logger.newline();
|
|
615
|
+
let created = 0;
|
|
616
|
+
let failed = 0;
|
|
617
|
+
for (const label of labels) {
|
|
618
|
+
try {
|
|
619
|
+
await withSpinner(`Creating ${colors.label(label.name)}`, async () => {
|
|
620
|
+
await createLabel(label);
|
|
621
|
+
});
|
|
622
|
+
created++;
|
|
623
|
+
} catch (error) {
|
|
624
|
+
logger.error(`Failed to create ${label.name}: ${error}`);
|
|
625
|
+
failed++;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
logger.newline();
|
|
629
|
+
logger.success(`Created ${created} labels`);
|
|
630
|
+
if (failed > 0) {
|
|
631
|
+
logger.warning(`Failed to create ${failed} labels`);
|
|
632
|
+
}
|
|
633
|
+
logger.newline();
|
|
634
|
+
logger.info("Labels are ready. You can now create AI-ready issues.");
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
export {
|
|
638
|
+
logger,
|
|
639
|
+
colors,
|
|
640
|
+
checkGhAuth,
|
|
641
|
+
checkClaudeCli,
|
|
642
|
+
checkGitRepo,
|
|
643
|
+
isValidIssueNumber,
|
|
644
|
+
sanitizeSlug,
|
|
645
|
+
getConfigPath,
|
|
646
|
+
loadConfig,
|
|
647
|
+
loadAgentInstructions,
|
|
648
|
+
configExists,
|
|
649
|
+
generateDefaultConfig,
|
|
650
|
+
withSpinner,
|
|
651
|
+
getWorkflowLabels,
|
|
652
|
+
buildIssueLabels,
|
|
653
|
+
extractTypeFromLabels,
|
|
654
|
+
extractPriorityFromLabels,
|
|
655
|
+
sortByPriority,
|
|
656
|
+
getIssue,
|
|
657
|
+
listIssues,
|
|
658
|
+
createIssue,
|
|
659
|
+
updateIssueLabels,
|
|
660
|
+
addIssueComment,
|
|
661
|
+
assignIssue,
|
|
662
|
+
createPullRequest,
|
|
663
|
+
getPrForBranch,
|
|
664
|
+
getCurrentUser,
|
|
665
|
+
setupLabelsCommand
|
|
666
|
+
};
|
|
667
|
+
//# sourceMappingURL=chunk-NRTQPDZB.js.map
|