@fractary/faber-cli 1.3.16 ā 1.4.1
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/dist/__mocks__/chalk.d.ts +16 -0
- package/dist/__mocks__/chalk.d.ts.map +1 -0
- package/dist/__mocks__/chalk.js +15 -0
- package/dist/commands/auth/index.d.ts.map +1 -1
- package/dist/commands/auth/index.js +26 -7
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +29 -2
- package/dist/commands/plan/index.d.ts.map +1 -1
- package/dist/commands/plan/index.js +78 -11
- package/dist/lib/sdk-type-adapter.d.ts +2 -0
- package/dist/lib/sdk-type-adapter.d.ts.map +1 -1
- package/dist/lib/sdk-type-adapter.js +2 -0
- package/dist/types/config.d.ts +8 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/utils/labels.d.ts +50 -0
- package/dist/utils/labels.d.ts.map +1 -0
- package/dist/utils/labels.js +182 -0
- package/dist/utils/sorting.d.ts +46 -0
- package/dist/utils/sorting.d.ts.map +1 -0
- package/dist/utils/sorting.js +89 -0
- package/package.json +1 -1
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock implementation of chalk for testing
|
|
3
|
+
*/
|
|
4
|
+
declare const mockChalk: {
|
|
5
|
+
green: (str: string) => string;
|
|
6
|
+
red: (str: string) => string;
|
|
7
|
+
yellow: (str: string) => string;
|
|
8
|
+
blue: (str: string) => string;
|
|
9
|
+
cyan: (str: string) => string;
|
|
10
|
+
gray: (str: string) => string;
|
|
11
|
+
grey: (str: string) => string;
|
|
12
|
+
bold: (str: string) => string;
|
|
13
|
+
dim: (str: string) => string;
|
|
14
|
+
};
|
|
15
|
+
export default mockChalk;
|
|
16
|
+
//# sourceMappingURL=chalk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chalk.d.ts","sourceRoot":"","sources":["../../src/__mocks__/chalk.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,QAAA,MAAM,SAAS;iBACA,MAAM;eACR,MAAM;kBACH,MAAM;gBACR,MAAM;gBACN,MAAM;gBACN,MAAM;gBACN,MAAM;gBACN,MAAM;eACP,MAAM;CAClB,CAAC;AAEF,eAAe,SAAS,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock implementation of chalk for testing
|
|
3
|
+
*/
|
|
4
|
+
const mockChalk = {
|
|
5
|
+
green: (str) => str,
|
|
6
|
+
red: (str) => str,
|
|
7
|
+
yellow: (str) => str,
|
|
8
|
+
blue: (str) => str,
|
|
9
|
+
cyan: (str) => str,
|
|
10
|
+
gray: (str) => str,
|
|
11
|
+
grey: (str) => str,
|
|
12
|
+
bold: (str) => str,
|
|
13
|
+
dim: (str) => str,
|
|
14
|
+
};
|
|
15
|
+
export default mockChalk;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA6BpC;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAehD;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA6BpC;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAehD;AA6ZD;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAM3C"}
|
|
@@ -102,27 +102,46 @@ async function runSetup(options) {
|
|
|
102
102
|
}
|
|
103
103
|
process.exit(1);
|
|
104
104
|
}
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
// Convert WSL path to Windows path if needed
|
|
106
|
+
let displayPath = htmlPath;
|
|
107
|
+
if (isWsl && htmlPath.startsWith('/mnt/')) {
|
|
108
|
+
// Convert /mnt/c/... to C:\...
|
|
109
|
+
const match = htmlPath.match(/^\/mnt\/([a-z])(\/.*)/);
|
|
110
|
+
if (match) {
|
|
111
|
+
const driveLetter = match[1].toUpperCase();
|
|
112
|
+
const windowsPath = match[2].replace(/\//g, '\\');
|
|
113
|
+
displayPath = `${driveLetter}:${windowsPath}`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
console.log(chalk.bold('š Manifest file created!\n'));
|
|
117
|
+
console.log(chalk.cyan('Open this file in your browser (copy the full path):\n'));
|
|
118
|
+
console.log(chalk.bold(` ${displayPath}\n`));
|
|
119
|
+
if (isWsl) {
|
|
120
|
+
console.log(chalk.gray('š” Tip: From Windows, you can also open it with:'));
|
|
121
|
+
console.log(chalk.gray(` - Press Win+R, paste the path above, press Enter`));
|
|
122
|
+
console.log(chalk.gray(` - Or open File Explorer and paste the path\n`));
|
|
123
|
+
}
|
|
124
|
+
// Try to open automatically, but don't block if it fails
|
|
107
125
|
const { execFile } = await import('child_process');
|
|
108
126
|
const { promisify } = await import('util');
|
|
109
127
|
const execFileAsync = promisify(execFile);
|
|
110
128
|
try {
|
|
111
129
|
if (process.platform === 'darwin') {
|
|
112
130
|
await execFileAsync('open', [htmlPath]);
|
|
131
|
+
console.log(chalk.gray('(Opened in default browser)\n'));
|
|
113
132
|
}
|
|
114
133
|
else if (process.platform === 'win32') {
|
|
115
|
-
// Windows requires cmd /c start for file associations
|
|
116
134
|
await execFileAsync('cmd', ['/c', 'start', '', htmlPath]);
|
|
135
|
+
console.log(chalk.gray('(Opened in default browser)\n'));
|
|
117
136
|
}
|
|
118
|
-
else {
|
|
119
|
-
// Linux
|
|
137
|
+
else if (!isWsl) {
|
|
138
|
+
// Only try xdg-open on native Linux, not WSL
|
|
120
139
|
await execFileAsync('xdg-open', [htmlPath]);
|
|
140
|
+
console.log(chalk.gray('(Opened in default browser)\n'));
|
|
121
141
|
}
|
|
122
142
|
}
|
|
123
143
|
catch (error) {
|
|
124
|
-
|
|
125
|
-
console.log(chalk.yellow(`Please open this file manually:\n ${htmlPath}\n`));
|
|
144
|
+
// Silently fail - we already printed the path above
|
|
126
145
|
}
|
|
127
146
|
console.log(chalk.bold('In your browser:'));
|
|
128
147
|
console.log(' 1. Review the app permissions');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,wBAAgB,iBAAiB,IAAI,OAAO,CAkG3C"}
|
package/dist/commands/init.js
CHANGED
|
@@ -5,6 +5,8 @@ import { Command } from 'commander';
|
|
|
5
5
|
import * as fs from 'fs/promises';
|
|
6
6
|
import * as path from 'path';
|
|
7
7
|
import chalk from 'chalk';
|
|
8
|
+
import { prompt } from '../utils/prompt.js';
|
|
9
|
+
import { createPriorityLabels, isGitHubCLIAvailable } from '../utils/labels.js';
|
|
8
10
|
export function createInitCommand() {
|
|
9
11
|
return new Command('workflow-init')
|
|
10
12
|
.description('Initialize a new FABER project')
|
|
@@ -44,20 +46,45 @@ logs/session-*.md
|
|
|
44
46
|
*.tmp
|
|
45
47
|
`;
|
|
46
48
|
await fs.writeFile(path.join(configDir, '.gitignore'), gitignore);
|
|
49
|
+
// Offer to create priority labels (if not in JSON mode)
|
|
50
|
+
let labelsCreated = false;
|
|
51
|
+
if (!options.json) {
|
|
52
|
+
const ghAvailable = await isGitHubCLIAvailable();
|
|
53
|
+
if (ghAvailable) {
|
|
54
|
+
console.log('');
|
|
55
|
+
const createLabels = await prompt('Create priority labels (priority-1 through priority-4) for backlog management? [Y/n]: ');
|
|
56
|
+
if (!createLabels || createLabels.toLowerCase() === 'y' || createLabels.toLowerCase() === 'yes') {
|
|
57
|
+
console.log(chalk.cyan('\nā Creating priority labels...'));
|
|
58
|
+
const result = await createPriorityLabels('priority', false);
|
|
59
|
+
if (result.created.length > 0) {
|
|
60
|
+
labelsCreated = true;
|
|
61
|
+
}
|
|
62
|
+
if (result.errors.length > 0) {
|
|
63
|
+
console.log(chalk.yellow('\nā ļø Some labels could not be created. You can create them manually later.'));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
47
68
|
if (options.json) {
|
|
48
69
|
console.log(JSON.stringify({
|
|
49
70
|
status: 'success',
|
|
50
|
-
data: { configPath, preset: options.preset },
|
|
71
|
+
data: { configPath, preset: options.preset, labelsCreated },
|
|
51
72
|
}, null, 2));
|
|
52
73
|
}
|
|
53
74
|
else {
|
|
54
|
-
console.log(chalk.green('ā FABER initialized successfully'));
|
|
75
|
+
console.log(chalk.green('\nā FABER initialized successfully'));
|
|
55
76
|
console.log(chalk.gray(` Config: ${configPath}`));
|
|
56
77
|
console.log(chalk.gray(` Preset: ${options.preset}`));
|
|
78
|
+
if (labelsCreated) {
|
|
79
|
+
console.log(chalk.gray(' Priority labels: Created'));
|
|
80
|
+
}
|
|
57
81
|
console.log('\nNext steps:');
|
|
58
82
|
console.log(' 1. Configure work tracking: Edit .fractary/faber/config.json');
|
|
59
83
|
console.log(' 2. Start a workflow: fractary-faber run --work-id <issue-number>');
|
|
60
84
|
console.log(' 3. Check status: fractary-faber status');
|
|
85
|
+
if (labelsCreated) {
|
|
86
|
+
console.log(' 4. Use priority labels: gh issue edit <number> --add-label "priority-1"');
|
|
87
|
+
}
|
|
61
88
|
}
|
|
62
89
|
}
|
|
63
90
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/plan/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/plan/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqDpC;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAqB3C"}
|
|
@@ -26,6 +26,9 @@ export function createPlanCommand() {
|
|
|
26
26
|
.option('--skip-confirm', 'Skip confirmation prompt (use with caution)')
|
|
27
27
|
.option('--output <format>', 'Output format: text|json|yaml', 'text')
|
|
28
28
|
.option('--json', 'Output as JSON (shorthand for --output json)')
|
|
29
|
+
.option('--limit <n>', 'Maximum number of issues to plan', parseInt)
|
|
30
|
+
.option('--order-by <strategy>', 'Order issues by: priority|created|updated (default: none)', 'none')
|
|
31
|
+
.option('--order-direction <dir>', 'Order direction: asc|desc (default: desc)', 'desc')
|
|
29
32
|
.action(async (options) => {
|
|
30
33
|
try {
|
|
31
34
|
await executePlanCommand(options);
|
|
@@ -39,7 +42,6 @@ export function createPlanCommand() {
|
|
|
39
42
|
* Main execution logic for plan command
|
|
40
43
|
*/
|
|
41
44
|
async function executePlanCommand(options) {
|
|
42
|
-
console.error('[DEBUG] Starting executePlanCommand');
|
|
43
45
|
const outputFormat = options.json ? 'json' : options.output || 'text';
|
|
44
46
|
// Validate arguments
|
|
45
47
|
if (!options.workId && !options.workLabel) {
|
|
@@ -48,14 +50,31 @@ async function executePlanCommand(options) {
|
|
|
48
50
|
if (options.workId && options.workLabel) {
|
|
49
51
|
throw new Error('Cannot use both --work-id and --work-label at the same time');
|
|
50
52
|
}
|
|
53
|
+
// Validate backlog management options
|
|
54
|
+
if (options.limit !== undefined) {
|
|
55
|
+
if (!Number.isInteger(options.limit) || options.limit <= 0) {
|
|
56
|
+
throw new Error('--limit must be a positive integer');
|
|
57
|
+
}
|
|
58
|
+
if (options.limit > 100) {
|
|
59
|
+
throw new Error('--limit cannot exceed 100');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (options.orderBy && options.orderBy !== 'none') {
|
|
63
|
+
const validOrderBy = ['priority', 'created', 'updated'];
|
|
64
|
+
if (!validOrderBy.includes(options.orderBy)) {
|
|
65
|
+
throw new Error(`--order-by must be one of: ${validOrderBy.join(', ')}, or 'none'`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (options.orderDirection) {
|
|
69
|
+
const validDirections = ['asc', 'desc'];
|
|
70
|
+
if (!validDirections.includes(options.orderDirection)) {
|
|
71
|
+
throw new Error(`--order-direction must be one of: ${validDirections.join(', ')}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
51
74
|
// Initialize clients
|
|
52
|
-
console.error('[DEBUG] Loading config...');
|
|
53
75
|
const config = await ConfigManager.load();
|
|
54
|
-
console.error('[DEBUG] Creating RepoClient...');
|
|
55
76
|
const repoClient = await RepoClient.create(config);
|
|
56
|
-
console.error('[DEBUG] Creating AnthropicClient...');
|
|
57
77
|
const anthropicClient = new AnthropicClient(config);
|
|
58
|
-
console.error('[DEBUG] Clients initialized');
|
|
59
78
|
if (outputFormat === 'text') {
|
|
60
79
|
console.log(chalk.blue('FABER CLI - Workflow Planning'));
|
|
61
80
|
console.log(chalk.gray('ā'.repeat(50)));
|
|
@@ -116,6 +135,41 @@ async function executePlanCommand(options) {
|
|
|
116
135
|
}
|
|
117
136
|
return;
|
|
118
137
|
}
|
|
138
|
+
// Auto-create priority labels if using priority ordering and labels don't exist
|
|
139
|
+
if (options.orderBy === 'priority') {
|
|
140
|
+
const { ensurePriorityLabels } = await import('../../utils/labels.js');
|
|
141
|
+
const priorityLabelPrefix = config.backlog_management?.priority_config?.label_prefix || 'priority';
|
|
142
|
+
// Try to ensure labels exist (quiet mode - won't spam console)
|
|
143
|
+
const labelsEnsured = await ensurePriorityLabels(priorityLabelPrefix, true);
|
|
144
|
+
if (outputFormat === 'text' && labelsEnsured) {
|
|
145
|
+
console.log(chalk.gray(`\nā Priority labels are ready (using prefix: ${priorityLabelPrefix})`));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Apply ordering if requested
|
|
149
|
+
if (options.orderBy && options.orderBy !== 'none') {
|
|
150
|
+
const { sortIssues } = await import('../../utils/sorting.js');
|
|
151
|
+
// Load priority label prefix from config (with fallback)
|
|
152
|
+
const priorityLabelPrefix = config.backlog_management?.priority_config?.label_prefix || 'priority';
|
|
153
|
+
const originalCount = issues.length;
|
|
154
|
+
issues = sortIssues(issues, {
|
|
155
|
+
orderBy: options.orderBy,
|
|
156
|
+
direction: (options.orderDirection || 'desc'),
|
|
157
|
+
priorityConfig: {
|
|
158
|
+
labelPrefix: priorityLabelPrefix,
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
if (outputFormat === 'text') {
|
|
162
|
+
console.log(chalk.blue(`\nā Sorted ${originalCount} issue(s) by ${options.orderBy} (${options.orderDirection || 'desc'})`));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Apply limit if specified
|
|
166
|
+
if (options.limit && issues.length > options.limit) {
|
|
167
|
+
const totalFound = issues.length;
|
|
168
|
+
issues = issues.slice(0, options.limit);
|
|
169
|
+
if (outputFormat === 'text') {
|
|
170
|
+
console.log(chalk.yellow(`ā Limiting to top ${options.limit} issue(s) (found ${totalFound})`));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
119
173
|
// Step 2: Extract workflows from labels or prompt user
|
|
120
174
|
if (outputFormat === 'text') {
|
|
121
175
|
console.log(chalk.cyan('\nā Identifying workflows...'));
|
|
@@ -403,14 +457,27 @@ function generatePlanComment(plan, workflow, worktreePath, planId) {
|
|
|
403
457
|
if (plan.phases && Array.isArray(plan.phases)) {
|
|
404
458
|
comment += `### Workflow Phases\n\n`;
|
|
405
459
|
plan.phases.forEach((phase, index) => {
|
|
406
|
-
comment += `**${index + 1}. ${phase.name || phase.phase}**\n`;
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
460
|
+
comment += `**${index + 1}. ${phase.name || phase.phase}**\n\n`;
|
|
461
|
+
// Show phase description if available
|
|
462
|
+
if (phase.description) {
|
|
463
|
+
comment += `*${phase.description}*\n\n`;
|
|
464
|
+
}
|
|
465
|
+
// Show steps/tasks
|
|
466
|
+
if (phase.steps && Array.isArray(phase.steps)) {
|
|
467
|
+
phase.steps.forEach((step) => {
|
|
468
|
+
const action = step.action || step.name || step.description || step;
|
|
469
|
+
comment += ` - **${action}**`;
|
|
470
|
+
if (step.details) {
|
|
471
|
+
comment += `: ${step.details}`;
|
|
472
|
+
}
|
|
473
|
+
comment += `\n`;
|
|
410
474
|
});
|
|
411
475
|
}
|
|
412
|
-
else if (phase.
|
|
413
|
-
|
|
476
|
+
else if (phase.tasks && Array.isArray(phase.tasks)) {
|
|
477
|
+
phase.tasks.forEach((task) => {
|
|
478
|
+
const taskDesc = task.description || task.name || task;
|
|
479
|
+
comment += ` - ${taskDesc}\n`;
|
|
480
|
+
});
|
|
414
481
|
}
|
|
415
482
|
comment += `\n`;
|
|
416
483
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdk-type-adapter.d.ts","sourceRoot":"","sources":["../../src/lib/sdk-type-adapter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,KAAK,IAAI,QAAQ,EAAE,QAAQ,IAAI,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGjF,UAAU,QAAQ;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"sdk-type-adapter.d.ts","sourceRoot":"","sources":["../../src/lib/sdk-type-adapter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,KAAK,IAAI,QAAQ,EAAE,QAAQ,IAAI,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGjF,UAAU,QAAQ;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,CAY/D;AAED;;;;;;;;GAQG;AACH,wBAAgB,8BAA8B,CAC5C,WAAW,EAAE,WAAW,EACxB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GACb,cAAc,CAUhB"}
|
package/dist/types/config.d.ts
CHANGED
|
@@ -32,11 +32,19 @@ export interface WorkflowConfig {
|
|
|
32
32
|
default?: string;
|
|
33
33
|
config_path?: string;
|
|
34
34
|
}
|
|
35
|
+
export interface BacklogManagementConfig {
|
|
36
|
+
default_limit?: number;
|
|
37
|
+
default_order_by?: 'priority' | 'created' | 'updated' | 'none';
|
|
38
|
+
priority_config?: {
|
|
39
|
+
label_prefix?: string;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
35
42
|
export interface FaberConfig {
|
|
36
43
|
anthropic?: AnthropicConfig;
|
|
37
44
|
github?: GitHubConfig;
|
|
38
45
|
worktree?: WorktreeConfig;
|
|
39
46
|
workflow?: WorkflowConfig;
|
|
47
|
+
backlog_management?: BacklogManagementConfig;
|
|
40
48
|
}
|
|
41
49
|
export interface ClaudeConfig {
|
|
42
50
|
worktree?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/types/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,WAAW,CAAC,EAAE,eAAe,GAAG,QAAQ,CAAC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,eAAe,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,QAAQ,CAAC,EAAE,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/types/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,WAAW,CAAC,EAAE,eAAe,GAAG,QAAQ,CAAC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,eAAe,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,uBAAuB;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;IAC/D,eAAe,CAAC,EAAE;QAChB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;CACH;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,kBAAkB,CAAC,EAAE,uBAAuB,CAAC;CAC9C;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE;QACT,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;CACH"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Label Management Utilities
|
|
3
|
+
*
|
|
4
|
+
* Handles creation and management of priority labels for backlog management
|
|
5
|
+
*/
|
|
6
|
+
export interface PriorityLabel {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
color: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Default priority labels (priority-1 through priority-4)
|
|
13
|
+
*/
|
|
14
|
+
export declare const DEFAULT_PRIORITY_LABELS: PriorityLabel[];
|
|
15
|
+
/**
|
|
16
|
+
* Generate priority labels based on a custom prefix
|
|
17
|
+
*/
|
|
18
|
+
export declare function generatePriorityLabels(prefix: string): PriorityLabel[];
|
|
19
|
+
/**
|
|
20
|
+
* Check if a label exists in the repository
|
|
21
|
+
* @param labelName - Name of the label to check
|
|
22
|
+
* @returns Promise resolving to true if label exists, false otherwise
|
|
23
|
+
*/
|
|
24
|
+
export declare function labelExists(labelName: string): Promise<boolean>;
|
|
25
|
+
/**
|
|
26
|
+
* Create a single label in the repository
|
|
27
|
+
* @param label - Label to create with name, description, and color
|
|
28
|
+
*/
|
|
29
|
+
export declare function createLabel(label: PriorityLabel): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Create multiple priority labels
|
|
32
|
+
* @param prefix - Label prefix (e.g., "priority" for priority-1, priority-2, etc.)
|
|
33
|
+
* @param quiet - If true, suppress console output
|
|
34
|
+
* @returns Object with arrays of created, skipped, and error labels
|
|
35
|
+
*/
|
|
36
|
+
export declare function createPriorityLabels(prefix?: string, quiet?: boolean): Promise<{
|
|
37
|
+
created: string[];
|
|
38
|
+
skipped: string[];
|
|
39
|
+
errors: string[];
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* Check if GitHub CLI is available
|
|
43
|
+
*/
|
|
44
|
+
export declare function isGitHubCLIAvailable(): Promise<boolean>;
|
|
45
|
+
/**
|
|
46
|
+
* Ensure priority labels exist, creating them if necessary
|
|
47
|
+
* This is a convenience function for automatic label creation
|
|
48
|
+
*/
|
|
49
|
+
export declare function ensurePriorityLabels(prefix?: string, quiet?: boolean): Promise<boolean>;
|
|
50
|
+
//# sourceMappingURL=labels.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"labels.d.ts","sourceRoot":"","sources":["../../src/utils/labels.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,eAAO,MAAM,uBAAuB,EAAE,aAAa,EAqBlD,CAAC;AAEF;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CAKtE;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CASrE;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAarE;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,GAAE,MAAmB,EAC3B,KAAK,GAAE,OAAe,GACrB,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAsDrE;AAED;;GAEG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO7D;AAaD;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,GAAE,MAAmB,EAC3B,KAAK,GAAE,OAAc,GACpB,OAAO,CAAC,OAAO,CAAC,CAqBlB"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Label Management Utilities
|
|
3
|
+
*
|
|
4
|
+
* Handles creation and management of priority labels for backlog management
|
|
5
|
+
*/
|
|
6
|
+
import { execFile } from 'child_process';
|
|
7
|
+
import { promisify } from 'util';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
10
|
+
/**
|
|
11
|
+
* Default priority labels (priority-1 through priority-4)
|
|
12
|
+
*/
|
|
13
|
+
export const DEFAULT_PRIORITY_LABELS = [
|
|
14
|
+
{
|
|
15
|
+
name: 'priority-1',
|
|
16
|
+
description: 'Highest priority - Critical issues that need immediate attention',
|
|
17
|
+
color: 'd73a4a', // Red
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'priority-2',
|
|
21
|
+
description: 'High priority - Important issues that should be addressed soon',
|
|
22
|
+
color: 'e99695', // Light red
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'priority-3',
|
|
26
|
+
description: 'Medium priority - Standard priority issues',
|
|
27
|
+
color: 'fbca04', // Yellow
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'priority-4',
|
|
31
|
+
description: 'Low priority - Nice to have, can be deferred',
|
|
32
|
+
color: 'd4c5f9', // Light purple
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
/**
|
|
36
|
+
* Generate priority labels based on a custom prefix
|
|
37
|
+
*/
|
|
38
|
+
export function generatePriorityLabels(prefix) {
|
|
39
|
+
return DEFAULT_PRIORITY_LABELS.map((label, index) => ({
|
|
40
|
+
...label,
|
|
41
|
+
name: `${prefix}-${index + 1}`,
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Check if a label exists in the repository
|
|
46
|
+
* @param labelName - Name of the label to check
|
|
47
|
+
* @returns Promise resolving to true if label exists, false otherwise
|
|
48
|
+
*/
|
|
49
|
+
export async function labelExists(labelName) {
|
|
50
|
+
try {
|
|
51
|
+
const { stdout } = await execFileAsync('gh', ['label', 'list', '--json', 'name', '--jq', '.[].name']);
|
|
52
|
+
const labels = stdout.trim().split('\n').map(l => l.trim()).filter(l => l.length > 0);
|
|
53
|
+
return labels.includes(labelName);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
// If gh command fails, assume label doesn't exist
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Create a single label in the repository
|
|
62
|
+
* @param label - Label to create with name, description, and color
|
|
63
|
+
*/
|
|
64
|
+
export async function createLabel(label) {
|
|
65
|
+
const { name, description, color } = label;
|
|
66
|
+
// Use execFileAsync with array arguments to prevent command injection
|
|
67
|
+
await execFileAsync('gh', [
|
|
68
|
+
'label',
|
|
69
|
+
'create',
|
|
70
|
+
name,
|
|
71
|
+
'--description',
|
|
72
|
+
description,
|
|
73
|
+
'--color',
|
|
74
|
+
color,
|
|
75
|
+
'--force'
|
|
76
|
+
]);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Create multiple priority labels
|
|
80
|
+
* @param prefix - Label prefix (e.g., "priority" for priority-1, priority-2, etc.)
|
|
81
|
+
* @param quiet - If true, suppress console output
|
|
82
|
+
* @returns Object with arrays of created, skipped, and error labels
|
|
83
|
+
*/
|
|
84
|
+
export async function createPriorityLabels(prefix = 'priority', quiet = false) {
|
|
85
|
+
// Validate prefix format
|
|
86
|
+
if (!isValidLabelPrefix(prefix)) {
|
|
87
|
+
return {
|
|
88
|
+
created: [],
|
|
89
|
+
skipped: [],
|
|
90
|
+
errors: [`Invalid label prefix: ${prefix}. Must contain only letters, numbers, and hyphens.`]
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const labels = generatePriorityLabels(prefix);
|
|
94
|
+
const created = [];
|
|
95
|
+
const skipped = [];
|
|
96
|
+
const errors = [];
|
|
97
|
+
// Fetch all existing labels once to optimize API calls
|
|
98
|
+
let existingLabels = [];
|
|
99
|
+
try {
|
|
100
|
+
const { stdout } = await execFileAsync('gh', ['label', 'list', '--json', 'name', '--jq', '.[].name']);
|
|
101
|
+
existingLabels = stdout.trim().split('\n').map(l => l.trim()).filter(l => l.length > 0);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
// If we can't fetch labels, proceed cautiously
|
|
105
|
+
if (!quiet) {
|
|
106
|
+
console.log(chalk.yellow(' ā ļø Could not fetch existing labels, will attempt creation'));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
for (const label of labels) {
|
|
110
|
+
try {
|
|
111
|
+
// Check if label already exists (using cached list)
|
|
112
|
+
if (existingLabels.includes(label.name)) {
|
|
113
|
+
skipped.push(label.name);
|
|
114
|
+
if (!quiet) {
|
|
115
|
+
console.log(chalk.gray(` ā³ Label already exists: ${label.name}`));
|
|
116
|
+
}
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
// Create the label
|
|
120
|
+
await createLabel(label);
|
|
121
|
+
created.push(label.name);
|
|
122
|
+
if (!quiet) {
|
|
123
|
+
console.log(chalk.green(` ā Created label: ${label.name}`));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
128
|
+
errors.push(`${label.name}: ${message}`);
|
|
129
|
+
if (!quiet) {
|
|
130
|
+
console.log(chalk.red(` ā Failed to create ${label.name}: ${message}`));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return { created, skipped, errors };
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Check if GitHub CLI is available
|
|
138
|
+
*/
|
|
139
|
+
export async function isGitHubCLIAvailable() {
|
|
140
|
+
try {
|
|
141
|
+
await execFileAsync('gh', ['--version']);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Validate label prefix format
|
|
150
|
+
* @param prefix - Label prefix to validate
|
|
151
|
+
* @returns True if valid, false otherwise
|
|
152
|
+
*/
|
|
153
|
+
function isValidLabelPrefix(prefix) {
|
|
154
|
+
// Label prefix should only contain lowercase letters, numbers, and hyphens
|
|
155
|
+
// and should not be empty
|
|
156
|
+
return /^[a-z0-9-]+$/i.test(prefix) && prefix.length > 0 && prefix.length <= 50;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Ensure priority labels exist, creating them if necessary
|
|
160
|
+
* This is a convenience function for automatic label creation
|
|
161
|
+
*/
|
|
162
|
+
export async function ensurePriorityLabels(prefix = 'priority', quiet = true) {
|
|
163
|
+
// Check if gh CLI is available
|
|
164
|
+
const ghAvailable = await isGitHubCLIAvailable();
|
|
165
|
+
if (!ghAvailable) {
|
|
166
|
+
if (!quiet) {
|
|
167
|
+
console.log(chalk.yellow(' ā ļø GitHub CLI (gh) not available, skipping label creation'));
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
const result = await createPriorityLabels(prefix, quiet);
|
|
173
|
+
// Return true if we created any labels or all were already present
|
|
174
|
+
return result.created.length > 0 || result.skipped.length > 0;
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
if (!quiet) {
|
|
178
|
+
console.log(chalk.yellow(` ā ļø Could not create priority labels: ${error instanceof Error ? error.message : String(error)}`));
|
|
179
|
+
}
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue Sorting Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides sorting and priority extraction for backlog management
|
|
5
|
+
*/
|
|
6
|
+
export interface SortOptions {
|
|
7
|
+
orderBy: 'priority' | 'created' | 'updated';
|
|
8
|
+
direction: 'asc' | 'desc';
|
|
9
|
+
priorityConfig: {
|
|
10
|
+
labelPrefix: string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export interface Issue {
|
|
14
|
+
id: string;
|
|
15
|
+
number: number;
|
|
16
|
+
title: string;
|
|
17
|
+
description: string;
|
|
18
|
+
labels: string[];
|
|
19
|
+
url: string;
|
|
20
|
+
state: string;
|
|
21
|
+
createdAt?: string;
|
|
22
|
+
updatedAt?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Sort issues according to strategy
|
|
26
|
+
*
|
|
27
|
+
* @param issues - Array of issues to sort
|
|
28
|
+
* @param options - Sort options including orderBy, direction, and priority config
|
|
29
|
+
* @returns Sorted array of issues
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* Priority sorting has inverted direction semantics compared to date sorting:
|
|
33
|
+
*
|
|
34
|
+
* **Priority Sorting:**
|
|
35
|
+
* - `direction: 'desc'` (default) = Highest priority first = priority-1, priority-2, priority-3, ...
|
|
36
|
+
* - `direction: 'asc'` = Lowest priority first = priority-4, priority-3, priority-2, priority-1
|
|
37
|
+
*
|
|
38
|
+
* This is because "descending priority" means "descending importance" (highest first),
|
|
39
|
+
* which corresponds to ascending numeric values (1 < 2 < 3).
|
|
40
|
+
*
|
|
41
|
+
* **Date Sorting:**
|
|
42
|
+
* - `direction: 'desc'` = Newest first (standard descending chronological order)
|
|
43
|
+
* - `direction: 'asc'` = Oldest first (standard ascending chronological order)
|
|
44
|
+
*/
|
|
45
|
+
export declare function sortIssues(issues: Issue[], options: SortOptions): Issue[];
|
|
46
|
+
//# sourceMappingURL=sorting.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sorting.d.ts","sourceRoot":"","sources":["../../src/utils/sorting.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;IAC5C,SAAS,EAAE,KAAK,GAAG,MAAM,CAAC;IAC1B,cAAc,EAAE;QACd,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;CACH;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA8CD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,WAAW,GAAG,KAAK,EAAE,CA2BzE"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue Sorting Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides sorting and priority extraction for backlog management
|
|
5
|
+
*/
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
/**
|
|
8
|
+
* Extract numeric priority from issue labels
|
|
9
|
+
* Examples: priority-1 ā 1, priority-2 ā 2, p-1 ā 1
|
|
10
|
+
* Returns 999 if no priority found (sorts last)
|
|
11
|
+
*
|
|
12
|
+
* @param labels - Array of label strings
|
|
13
|
+
* @param prefix - Label prefix to match (e.g., "priority")
|
|
14
|
+
* @param issueNumber - Optional issue number for warning messages
|
|
15
|
+
* @returns Priority number (1-4 for valid priorities, 999 for no/invalid priority)
|
|
16
|
+
*/
|
|
17
|
+
function extractPriority(labels, prefix, issueNumber) {
|
|
18
|
+
// Use stricter pattern matching: prefix must be followed by hyphen and digits
|
|
19
|
+
const pattern = new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}-\\d+$`, 'i');
|
|
20
|
+
const priorityLabels = labels.filter(l => pattern.test(l));
|
|
21
|
+
if (priorityLabels.length === 0) {
|
|
22
|
+
return 999; // No priority label found
|
|
23
|
+
}
|
|
24
|
+
// Warn if multiple priority labels found
|
|
25
|
+
if (priorityLabels.length > 1) {
|
|
26
|
+
const issueRef = issueNumber ? `Issue #${issueNumber}` : 'An issue';
|
|
27
|
+
console.log(chalk.yellow(`ā ļø ${issueRef} has multiple priority labels: ${priorityLabels.join(', ')}. Using first match: ${priorityLabels[0]}`));
|
|
28
|
+
}
|
|
29
|
+
// Extract numeric part: "priority-1" ā "1"
|
|
30
|
+
const match = priorityLabels[0].match(/-(\d+)$/);
|
|
31
|
+
if (!match) {
|
|
32
|
+
return 999; // Shouldn't happen with regex, but be safe
|
|
33
|
+
}
|
|
34
|
+
const numeric = parseInt(match[1], 10);
|
|
35
|
+
// Validate priority is in reasonable range (1-10)
|
|
36
|
+
// We allow up to 10 to be flexible, but typical range is 1-4
|
|
37
|
+
if (isNaN(numeric) || numeric < 1 || numeric > 10) {
|
|
38
|
+
const issueRef = issueNumber ? `Issue #${issueNumber}` : 'An issue';
|
|
39
|
+
console.log(chalk.yellow(`ā ļø ${issueRef} has invalid priority value: ${priorityLabels[0]}. Priority should be between 1-10.`));
|
|
40
|
+
return 999;
|
|
41
|
+
}
|
|
42
|
+
return numeric;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Sort issues according to strategy
|
|
46
|
+
*
|
|
47
|
+
* @param issues - Array of issues to sort
|
|
48
|
+
* @param options - Sort options including orderBy, direction, and priority config
|
|
49
|
+
* @returns Sorted array of issues
|
|
50
|
+
*
|
|
51
|
+
* @remarks
|
|
52
|
+
* Priority sorting has inverted direction semantics compared to date sorting:
|
|
53
|
+
*
|
|
54
|
+
* **Priority Sorting:**
|
|
55
|
+
* - `direction: 'desc'` (default) = Highest priority first = priority-1, priority-2, priority-3, ...
|
|
56
|
+
* - `direction: 'asc'` = Lowest priority first = priority-4, priority-3, priority-2, priority-1
|
|
57
|
+
*
|
|
58
|
+
* This is because "descending priority" means "descending importance" (highest first),
|
|
59
|
+
* which corresponds to ascending numeric values (1 < 2 < 3).
|
|
60
|
+
*
|
|
61
|
+
* **Date Sorting:**
|
|
62
|
+
* - `direction: 'desc'` = Newest first (standard descending chronological order)
|
|
63
|
+
* - `direction: 'asc'` = Oldest first (standard ascending chronological order)
|
|
64
|
+
*/
|
|
65
|
+
export function sortIssues(issues, options) {
|
|
66
|
+
const sorted = [...issues];
|
|
67
|
+
sorted.sort((a, b) => {
|
|
68
|
+
let comparison = 0;
|
|
69
|
+
if (options.orderBy === 'priority') {
|
|
70
|
+
// Pass issue numbers for better warning messages
|
|
71
|
+
const aPriority = extractPriority(a.labels, options.priorityConfig.labelPrefix, a.number);
|
|
72
|
+
const bPriority = extractPriority(b.labels, options.priorityConfig.labelPrefix, b.number);
|
|
73
|
+
comparison = aPriority - bPriority; // Lower number = higher priority
|
|
74
|
+
// For priority, direction is inverted:
|
|
75
|
+
// - desc (default) = highest priority first = lowest number first = use comparison as-is
|
|
76
|
+
// - asc = lowest priority first = highest number first = negate comparison
|
|
77
|
+
return options.direction === 'asc' ? -comparison : comparison;
|
|
78
|
+
}
|
|
79
|
+
else if (options.orderBy === 'created' && a.createdAt && b.createdAt) {
|
|
80
|
+
comparison = new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
|
81
|
+
}
|
|
82
|
+
else if (options.orderBy === 'updated' && a.updatedAt && b.updatedAt) {
|
|
83
|
+
comparison = new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime();
|
|
84
|
+
}
|
|
85
|
+
// Apply direction for dates (standard: asc = oldest first, desc = newest first)
|
|
86
|
+
return options.direction === 'asc' ? comparison : -comparison;
|
|
87
|
+
});
|
|
88
|
+
return sorted;
|
|
89
|
+
}
|