@proletariat/cli 0.3.30 → 0.3.32
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/commands/diet.d.ts +20 -0
- package/dist/commands/diet.js +181 -0
- package/dist/commands/mcp-server.js +2 -1
- package/dist/commands/priority/add.d.ts +15 -0
- package/dist/commands/priority/add.js +70 -0
- package/dist/commands/priority/list.d.ts +10 -0
- package/dist/commands/priority/list.js +34 -0
- package/dist/commands/priority/remove.d.ts +13 -0
- package/dist/commands/priority/remove.js +54 -0
- package/dist/commands/priority/set.d.ts +14 -0
- package/dist/commands/priority/set.js +60 -0
- package/dist/commands/pull.d.ts +23 -0
- package/dist/commands/pull.js +219 -0
- package/dist/commands/roadmap/generate.js +10 -5
- package/dist/commands/template/apply.js +5 -4
- package/dist/commands/template/create.js +9 -5
- package/dist/commands/ticket/create.js +6 -5
- package/dist/commands/ticket/edit.js +9 -9
- package/dist/commands/ticket/list.d.ts +2 -0
- package/dist/commands/ticket/list.js +20 -13
- package/dist/commands/ticket/update.js +8 -5
- package/dist/commands/work/spawn.d.ts +13 -0
- package/dist/commands/work/spawn.js +388 -1
- package/dist/lib/mcp/tools/diet.d.ts +6 -0
- package/dist/lib/mcp/tools/diet.js +261 -0
- package/dist/lib/mcp/tools/index.d.ts +1 -0
- package/dist/lib/mcp/tools/index.js +1 -0
- package/dist/lib/mcp/tools/template.js +1 -1
- package/dist/lib/mcp/tools/ticket.js +48 -3
- package/dist/lib/pmo/diet.d.ts +102 -0
- package/dist/lib/pmo/diet.js +127 -0
- package/dist/lib/pmo/storage/base.d.ts +5 -0
- package/dist/lib/pmo/storage/base.js +47 -0
- package/dist/lib/pmo/types.d.ts +12 -6
- package/dist/lib/pmo/types.js +6 -2
- package/dist/lib/pmo/utils.d.ts +40 -0
- package/dist/lib/pmo/utils.js +76 -0
- package/oclif.manifest.json +2872 -2534
- package/package.json +1 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { PMOCommand } from '../lib/pmo/base-command.js';
|
|
2
|
+
export default class Diet extends PMOCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
set: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
reset: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
protected execute(): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Build a diet report comparing actual Ready distribution to targets.
|
|
14
|
+
*/
|
|
15
|
+
private buildDietReport;
|
|
16
|
+
/**
|
|
17
|
+
* Display the diet report with visual indicators.
|
|
18
|
+
*/
|
|
19
|
+
private displayDietReport;
|
|
20
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { PMOCommand, pmoBaseFlags } from '../lib/pmo/base-command.js';
|
|
3
|
+
import { styles, divider } from '../lib/styles.js';
|
|
4
|
+
import { loadDietConfig, saveDietConfig, parseDietString, formatDietConfig, } from '../lib/pmo/diet.js';
|
|
5
|
+
export default class Diet extends PMOCommand {
|
|
6
|
+
static description = 'Show or configure diet ratios for balanced ticket distribution';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> <%= command.id %>',
|
|
9
|
+
'<%= config.bin %> <%= command.id %> --set "ship=4,grow=2.5,support=1.5,bizops=1,strategy=1"',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> --reset',
|
|
11
|
+
];
|
|
12
|
+
static flags = {
|
|
13
|
+
...pmoBaseFlags,
|
|
14
|
+
set: Flags.string({
|
|
15
|
+
char: 's',
|
|
16
|
+
description: 'Set diet weights (e.g., "ship=4,grow=2,support=1") - normalized to percentages automatically',
|
|
17
|
+
}),
|
|
18
|
+
reset: Flags.boolean({
|
|
19
|
+
char: 'r',
|
|
20
|
+
description: 'Reset diet to default ratios',
|
|
21
|
+
default: false,
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
async execute() {
|
|
25
|
+
const { flags } = await this.parse(Diet);
|
|
26
|
+
const projectId = await this.requireProject();
|
|
27
|
+
const db = this.storage.getDatabase();
|
|
28
|
+
// Handle --set
|
|
29
|
+
if (flags.set) {
|
|
30
|
+
try {
|
|
31
|
+
const newConfig = parseDietString(flags.set);
|
|
32
|
+
saveDietConfig(db, newConfig);
|
|
33
|
+
this.log(styles.success(`Diet updated: ${formatDietConfig(newConfig)}`));
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
37
|
+
this.error(message);
|
|
38
|
+
}
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// Handle --reset
|
|
42
|
+
if (flags.reset) {
|
|
43
|
+
const { DEFAULT_DIET_CONFIG } = await import('../lib/pmo/diet.js');
|
|
44
|
+
saveDietConfig(db, DEFAULT_DIET_CONFIG);
|
|
45
|
+
this.log(styles.success(`Diet reset to defaults: ${formatDietConfig(DEFAULT_DIET_CONFIG)}`));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// Show diet report
|
|
49
|
+
const dietConfig = loadDietConfig(db);
|
|
50
|
+
const report = await this.buildDietReport(projectId, dietConfig);
|
|
51
|
+
this.displayDietReport(report, dietConfig);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build a diet report comparing actual Ready distribution to targets.
|
|
55
|
+
*/
|
|
56
|
+
async buildDietReport(projectId, dietConfig) {
|
|
57
|
+
// Get all ready/unstarted tickets using statusCategory filter
|
|
58
|
+
const readyTickets = await this.storage.listTickets(projectId, { statusCategory: 'unstarted' });
|
|
59
|
+
const totalReady = readyTickets.length;
|
|
60
|
+
// Count by category
|
|
61
|
+
const categoryCounts = new Map();
|
|
62
|
+
let uncategorized = 0;
|
|
63
|
+
for (const ticket of readyTickets) {
|
|
64
|
+
const cat = (ticket.category || '').toLowerCase();
|
|
65
|
+
if (cat) {
|
|
66
|
+
categoryCounts.set(cat, (categoryCounts.get(cat) || 0) + 1);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
uncategorized++;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Build category reports
|
|
73
|
+
const categories = dietConfig.ratios.map(ratio => {
|
|
74
|
+
const count = categoryCounts.get(ratio.category) || 0;
|
|
75
|
+
const actual = totalReady > 0 ? count / totalReady : 0;
|
|
76
|
+
const targetCount = Math.round(totalReady * ratio.target);
|
|
77
|
+
const delta = actual - ratio.target;
|
|
78
|
+
const tolerance = 0.05; // 5% tolerance
|
|
79
|
+
let status = 'ok';
|
|
80
|
+
if (delta > tolerance)
|
|
81
|
+
status = 'over';
|
|
82
|
+
else if (delta < -tolerance)
|
|
83
|
+
status = 'under';
|
|
84
|
+
return {
|
|
85
|
+
category: ratio.category,
|
|
86
|
+
target: ratio.target,
|
|
87
|
+
actual,
|
|
88
|
+
count,
|
|
89
|
+
targetCount,
|
|
90
|
+
delta,
|
|
91
|
+
status,
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
// Check for categories not in the diet config
|
|
95
|
+
for (const [cat, count] of categoryCounts) {
|
|
96
|
+
if (!dietConfig.ratios.some(r => r.category === cat)) {
|
|
97
|
+
const actual = totalReady > 0 ? count / totalReady : 0;
|
|
98
|
+
categories.push({
|
|
99
|
+
category: cat,
|
|
100
|
+
target: 0,
|
|
101
|
+
actual,
|
|
102
|
+
count,
|
|
103
|
+
targetCount: 0,
|
|
104
|
+
delta: actual,
|
|
105
|
+
status: actual > 0.05 ? 'over' : 'ok',
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return { categories, totalReady, uncategorized };
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Display the diet report with visual indicators.
|
|
113
|
+
*/
|
|
114
|
+
displayDietReport(report, dietConfig) {
|
|
115
|
+
this.log(styles.title('\nDiet Report'));
|
|
116
|
+
this.log(divider(65));
|
|
117
|
+
this.log(` Current diet: ${formatDietConfig(dietConfig)}`);
|
|
118
|
+
this.log(` Total in Ready: ${report.totalReady}`);
|
|
119
|
+
if (report.uncategorized > 0) {
|
|
120
|
+
this.log(styles.warning(` Uncategorized: ${report.uncategorized}`));
|
|
121
|
+
}
|
|
122
|
+
this.log(divider(65));
|
|
123
|
+
if (report.totalReady === 0) {
|
|
124
|
+
this.log(styles.muted('\n No tickets in Ready. Run "prlt pull" to populate.\n'));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
// Header
|
|
128
|
+
this.log('');
|
|
129
|
+
this.log(` ${'Category'.padEnd(14)} ${'Target'.padEnd(8)} ${'Actual'.padEnd(8)} ${'Count'.padEnd(7)} ${'Status'.padEnd(8)} ${'Bar'}`);
|
|
130
|
+
this.log(` ${divider(62).trim()}`);
|
|
131
|
+
// Category rows
|
|
132
|
+
for (const cat of report.categories) {
|
|
133
|
+
const targetPct = `${Math.round(cat.target * 100)}%`;
|
|
134
|
+
const actualPct = `${Math.round(cat.actual * 100)}%`;
|
|
135
|
+
const countStr = `${cat.count}/${cat.targetCount}`;
|
|
136
|
+
let statusIndicator;
|
|
137
|
+
switch (cat.status) {
|
|
138
|
+
case 'over':
|
|
139
|
+
statusIndicator = styles.warning('OVER');
|
|
140
|
+
break;
|
|
141
|
+
case 'under':
|
|
142
|
+
statusIndicator = styles.error('UNDER');
|
|
143
|
+
break;
|
|
144
|
+
default:
|
|
145
|
+
statusIndicator = styles.success('OK');
|
|
146
|
+
}
|
|
147
|
+
// Visual bar (max 20 chars)
|
|
148
|
+
const barWidth = 20;
|
|
149
|
+
const actualBar = Math.round(cat.actual * barWidth);
|
|
150
|
+
const targetBar = Math.round(cat.target * barWidth);
|
|
151
|
+
let bar = '';
|
|
152
|
+
for (let i = 0; i < barWidth; i++) {
|
|
153
|
+
if (i < actualBar && i < targetBar) {
|
|
154
|
+
bar += styles.success('█');
|
|
155
|
+
}
|
|
156
|
+
else if (i < actualBar) {
|
|
157
|
+
bar += styles.warning('█');
|
|
158
|
+
}
|
|
159
|
+
else if (i < targetBar) {
|
|
160
|
+
bar += styles.muted('░');
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
bar += ' ';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
this.log(` ${cat.category.padEnd(14)} ${targetPct.padEnd(8)} ${actualPct.padEnd(8)} ${countStr.padEnd(7)} ${statusIndicator.padEnd(8 + 10)} ${bar}`);
|
|
167
|
+
}
|
|
168
|
+
// Warnings
|
|
169
|
+
const warnings = report.categories.filter(c => c.status !== 'ok');
|
|
170
|
+
if (warnings.length > 0) {
|
|
171
|
+
this.log('');
|
|
172
|
+
this.log(styles.warning(' Warnings:'));
|
|
173
|
+
for (const w of warnings) {
|
|
174
|
+
const direction = w.status === 'over' ? 'over' : 'under';
|
|
175
|
+
const delta = Math.abs(Math.round(w.delta * 100));
|
|
176
|
+
this.log(styles.warning(` ${w.category}: ${delta}% ${direction}-represented`));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
this.log('');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -19,7 +19,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
19
19
|
import { execSync } from 'node:child_process';
|
|
20
20
|
import * as path from 'node:path';
|
|
21
21
|
import { getPMOContext } from '../lib/pmo/pmo-context.js';
|
|
22
|
-
import { registerTicketTools, registerProjectTools, registerBoardTools, registerSpecTools, registerEpicTools, registerWorkTools, registerWorkflowTools, registerStatusTools, registerPhaseTools, registerActionTools, registerRoadmapTools, registerCategoryTools, registerTemplateTools, registerViewTools, registerAgentTools, registerDockerTools, registerRepoTools, registerBranchTools, registerGitHubTools, registerInitTools, registerUtilityTools, } from '../lib/mcp/index.js';
|
|
22
|
+
import { registerTicketTools, registerProjectTools, registerBoardTools, registerSpecTools, registerEpicTools, registerWorkTools, registerWorkflowTools, registerStatusTools, registerPhaseTools, registerActionTools, registerRoadmapTools, registerCategoryTools, registerTemplateTools, registerViewTools, registerDietTools, registerAgentTools, registerDockerTools, registerRepoTools, registerBranchTools, registerGitHubTools, registerInitTools, registerUtilityTools, } from '../lib/mcp/index.js';
|
|
23
23
|
export default class McpServerCommand extends Command {
|
|
24
24
|
static description = 'Start MCP server for AI agent integration (exposes all prlt commands as tools)';
|
|
25
25
|
static hidden = true;
|
|
@@ -84,6 +84,7 @@ export default class McpServerCommand extends Command {
|
|
|
84
84
|
registerCategoryTools(server, ctx);
|
|
85
85
|
registerTemplateTools(server, ctx);
|
|
86
86
|
registerViewTools(server, ctx);
|
|
87
|
+
registerDietTools(server, ctx);
|
|
87
88
|
registerAgentTools(server, ctx);
|
|
88
89
|
registerDockerTools(server, ctx);
|
|
89
90
|
registerRepoTools(server, ctx);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
export default class PriorityAdd extends PMOCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
value: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
position: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
after: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
execute(): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
3
|
+
import { styles } from '../../lib/styles.js';
|
|
4
|
+
import { getWorkspacePriorities, setWorkspacePriorities } from '../../lib/pmo/utils.js';
|
|
5
|
+
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
6
|
+
export default class PriorityAdd extends PMOCommand {
|
|
7
|
+
static description = 'Add a priority value to the workspace scale';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> <%= command.id %> Critical',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> "Must Have" --position 0',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> P4 --after P3',
|
|
12
|
+
];
|
|
13
|
+
static args = {
|
|
14
|
+
value: Args.string({
|
|
15
|
+
description: 'Priority value to add',
|
|
16
|
+
required: true,
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
19
|
+
static flags = {
|
|
20
|
+
...pmoBaseFlags,
|
|
21
|
+
position: Flags.integer({
|
|
22
|
+
description: 'Position in the priority scale (0 = highest)',
|
|
23
|
+
}),
|
|
24
|
+
after: Flags.string({
|
|
25
|
+
description: 'Insert after this priority value',
|
|
26
|
+
exclusive: ['position'],
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
async execute() {
|
|
30
|
+
const { args, flags } = await this.parse(PriorityAdd);
|
|
31
|
+
const jsonMode = shouldOutputJson(flags);
|
|
32
|
+
const db = this.storage.getDatabase();
|
|
33
|
+
const priorities = getWorkspacePriorities(db);
|
|
34
|
+
// Check for duplicates
|
|
35
|
+
if (priorities.includes(args.value)) {
|
|
36
|
+
if (jsonMode) {
|
|
37
|
+
outputErrorAsJson('DUPLICATE_PRIORITY', `Priority "${args.value}" already exists`, createMetadata('priority add', flags));
|
|
38
|
+
this.exit(1);
|
|
39
|
+
}
|
|
40
|
+
this.error(`Priority "${args.value}" already exists in the scale: ${priorities.join(', ')}`);
|
|
41
|
+
}
|
|
42
|
+
// Determine insertion position
|
|
43
|
+
let insertAt = priorities.length; // Default: append to end (lowest priority)
|
|
44
|
+
if (flags.position !== undefined) {
|
|
45
|
+
insertAt = Math.max(0, Math.min(flags.position, priorities.length));
|
|
46
|
+
}
|
|
47
|
+
else if (flags.after) {
|
|
48
|
+
const afterIndex = priorities.indexOf(flags.after);
|
|
49
|
+
if (afterIndex < 0) {
|
|
50
|
+
if (jsonMode) {
|
|
51
|
+
outputErrorAsJson('PRIORITY_NOT_FOUND', `Priority "${flags.after}" not found`, createMetadata('priority add', flags));
|
|
52
|
+
this.exit(1);
|
|
53
|
+
}
|
|
54
|
+
this.error(`Priority "${flags.after}" not found in the scale: ${priorities.join(', ')}`);
|
|
55
|
+
}
|
|
56
|
+
insertAt = afterIndex + 1;
|
|
57
|
+
}
|
|
58
|
+
// Insert at position
|
|
59
|
+
const newPriorities = [...priorities];
|
|
60
|
+
newPriorities.splice(insertAt, 0, args.value);
|
|
61
|
+
setWorkspacePriorities(db, newPriorities);
|
|
62
|
+
if (jsonMode) {
|
|
63
|
+
this.log(JSON.stringify({ success: true, priorities: newPriorities }, null, 2));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
this.log(styles.success(`\n✅ Added priority "${args.value}" at position ${insertAt}`));
|
|
67
|
+
this.log(styles.muted(` Scale: ${newPriorities.join(', ')}`));
|
|
68
|
+
this.log('');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
export default class PriorityList extends PMOCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
};
|
|
9
|
+
execute(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
2
|
+
import { styles } from '../../lib/styles.js';
|
|
3
|
+
import { getWorkspacePriorities } from '../../lib/pmo/utils.js';
|
|
4
|
+
import { isMachineOutput } from '../../lib/prompt-json.js';
|
|
5
|
+
export default class PriorityList extends PMOCommand {
|
|
6
|
+
static description = 'List the workspace priority scale';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> <%= command.id %>',
|
|
9
|
+
'<%= config.bin %> <%= command.id %> --json',
|
|
10
|
+
];
|
|
11
|
+
static flags = {
|
|
12
|
+
...pmoBaseFlags,
|
|
13
|
+
};
|
|
14
|
+
async execute() {
|
|
15
|
+
const { flags } = await this.parse(PriorityList);
|
|
16
|
+
const db = this.storage.getDatabase();
|
|
17
|
+
const priorities = getWorkspacePriorities(db);
|
|
18
|
+
if (isMachineOutput(flags)) {
|
|
19
|
+
this.log(JSON.stringify({ priorities }, null, 2));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
this.log(`\n${styles.emphasis('Priority Scale')} (highest to lowest)`);
|
|
23
|
+
this.log('─'.repeat(40));
|
|
24
|
+
for (let i = 0; i < priorities.length; i++) {
|
|
25
|
+
const marker = i === 0 ? '🔴' : i === 1 ? '🟠' : i === 2 ? '🟡' : '🟢';
|
|
26
|
+
this.log(` ${marker} ${priorities[i]}`);
|
|
27
|
+
}
|
|
28
|
+
this.log('');
|
|
29
|
+
this.log(styles.muted('Manage: prlt priority set <values...>'));
|
|
30
|
+
this.log(styles.muted(' prlt priority add <value>'));
|
|
31
|
+
this.log(styles.muted(' prlt priority remove <value>'));
|
|
32
|
+
this.log('');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
export default class PriorityRemove extends PMOCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
value: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
execute(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
|
+
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
3
|
+
import { styles } from '../../lib/styles.js';
|
|
4
|
+
import { getWorkspacePriorities, setWorkspacePriorities } from '../../lib/pmo/utils.js';
|
|
5
|
+
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
6
|
+
export default class PriorityRemove extends PMOCommand {
|
|
7
|
+
static description = 'Remove a priority value from the workspace scale';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> <%= command.id %> P3',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> Low',
|
|
11
|
+
];
|
|
12
|
+
static args = {
|
|
13
|
+
value: Args.string({
|
|
14
|
+
description: 'Priority value to remove',
|
|
15
|
+
required: true,
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
static flags = {
|
|
19
|
+
...pmoBaseFlags,
|
|
20
|
+
};
|
|
21
|
+
async execute() {
|
|
22
|
+
const { args, flags } = await this.parse(PriorityRemove);
|
|
23
|
+
const jsonMode = shouldOutputJson(flags);
|
|
24
|
+
const db = this.storage.getDatabase();
|
|
25
|
+
const priorities = getWorkspacePriorities(db);
|
|
26
|
+
// Check that the priority exists
|
|
27
|
+
if (!priorities.includes(args.value)) {
|
|
28
|
+
if (jsonMode) {
|
|
29
|
+
outputErrorAsJson('PRIORITY_NOT_FOUND', `Priority "${args.value}" not found`, createMetadata('priority remove', flags));
|
|
30
|
+
this.exit(1);
|
|
31
|
+
}
|
|
32
|
+
this.error(`Priority "${args.value}" not found in the scale: ${priorities.join(', ')}`);
|
|
33
|
+
}
|
|
34
|
+
// Must keep at least one priority
|
|
35
|
+
if (priorities.length <= 1) {
|
|
36
|
+
if (jsonMode) {
|
|
37
|
+
outputErrorAsJson('MIN_PRIORITIES', 'Cannot remove the last priority. Use "prlt priority set" to replace the scale.', createMetadata('priority remove', flags));
|
|
38
|
+
this.exit(1);
|
|
39
|
+
}
|
|
40
|
+
this.error('Cannot remove the last priority. Use "prlt priority set" to replace the scale.');
|
|
41
|
+
}
|
|
42
|
+
const newPriorities = priorities.filter(p => p !== args.value);
|
|
43
|
+
setWorkspacePriorities(db, newPriorities);
|
|
44
|
+
if (jsonMode) {
|
|
45
|
+
this.log(JSON.stringify({ success: true, priorities: newPriorities, removed: args.value }, null, 2));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
this.log(styles.success(`\n✅ Removed priority "${args.value}"`));
|
|
49
|
+
this.log(styles.muted(` Scale: ${newPriorities.join(', ')}`));
|
|
50
|
+
this.log(styles.muted('\n Note: Existing tickets with this priority are not affected.'));
|
|
51
|
+
this.log(styles.muted(' You may want to update them manually.'));
|
|
52
|
+
this.log('');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
export default class PrioritySet extends PMOCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static strict: boolean;
|
|
6
|
+
static args: {
|
|
7
|
+
priorities: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
static flags: {
|
|
10
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
execute(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
|
+
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
3
|
+
import { styles } from '../../lib/styles.js';
|
|
4
|
+
import { getWorkspacePriorities, setWorkspacePriorities } from '../../lib/pmo/utils.js';
|
|
5
|
+
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
6
|
+
export default class PrioritySet extends PMOCommand {
|
|
7
|
+
static description = 'Set the workspace priority scale (replaces all existing priorities)';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> <%= command.id %> P0 P1 P2 P3',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> Critical High Medium Low',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> Now Next Later',
|
|
12
|
+
'<%= config.bin %> <%= command.id %> "Must Have" "Should Have" "Nice to Have"',
|
|
13
|
+
];
|
|
14
|
+
static strict = false;
|
|
15
|
+
static args = {
|
|
16
|
+
priorities: Args.string({
|
|
17
|
+
description: 'Priority values from highest to lowest (space-separated)',
|
|
18
|
+
required: false,
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
static flags = {
|
|
22
|
+
...pmoBaseFlags,
|
|
23
|
+
};
|
|
24
|
+
async execute() {
|
|
25
|
+
const { flags, argv } = await this.parse(PrioritySet);
|
|
26
|
+
const jsonMode = shouldOutputJson(flags);
|
|
27
|
+
const db = this.storage.getDatabase();
|
|
28
|
+
// argv contains all the non-flag arguments
|
|
29
|
+
const newPriorities = argv.filter(a => a.trim());
|
|
30
|
+
if (newPriorities.length === 0) {
|
|
31
|
+
if (jsonMode) {
|
|
32
|
+
outputErrorAsJson('NO_PRIORITIES', 'At least one priority value is required', createMetadata('priority set', flags));
|
|
33
|
+
this.exit(1);
|
|
34
|
+
}
|
|
35
|
+
this.error('At least one priority value is required.\n\nUsage: prlt priority set <value1> <value2> ...\nExample: prlt priority set P0 P1 P2 P3');
|
|
36
|
+
}
|
|
37
|
+
// Check for duplicates
|
|
38
|
+
const seen = new Set();
|
|
39
|
+
for (const p of newPriorities) {
|
|
40
|
+
if (seen.has(p)) {
|
|
41
|
+
if (jsonMode) {
|
|
42
|
+
outputErrorAsJson('DUPLICATE_PRIORITY', `Duplicate priority value: "${p}"`, createMetadata('priority set', flags));
|
|
43
|
+
this.exit(1);
|
|
44
|
+
}
|
|
45
|
+
this.error(`Duplicate priority value: "${p}". Each priority must be unique.`);
|
|
46
|
+
}
|
|
47
|
+
seen.add(p);
|
|
48
|
+
}
|
|
49
|
+
const oldPriorities = getWorkspacePriorities(db);
|
|
50
|
+
setWorkspacePriorities(db, newPriorities);
|
|
51
|
+
if (jsonMode) {
|
|
52
|
+
this.log(JSON.stringify({ success: true, priorities: newPriorities }, null, 2));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
this.log(styles.success(`\n✅ Priority scale updated`));
|
|
56
|
+
this.log(styles.muted(` Before: ${oldPriorities.join(', ')}`));
|
|
57
|
+
this.log(styles.muted(` After: ${newPriorities.join(', ')}`));
|
|
58
|
+
this.log('');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { PMOCommand } from '../lib/pmo/base-command.js';
|
|
2
|
+
export default class Pull extends PMOCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
count: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
protected execute(): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Core pull algorithm with diet enforcement.
|
|
14
|
+
*
|
|
15
|
+
* Pass 1: Walk backlog top-down, pull if category not over ceiling
|
|
16
|
+
* Pass 2: Force-pull from underrepresented categories
|
|
17
|
+
*/
|
|
18
|
+
private runPullAlgorithm;
|
|
19
|
+
/**
|
|
20
|
+
* Display pull results.
|
|
21
|
+
*/
|
|
22
|
+
private displayPullResults;
|
|
23
|
+
}
|