@proletariat/cli 0.3.9 → 0.3.11
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 +25 -0
- package/bin/dev.js +0 -0
- package/dist/commands/action/index.js +1 -1
- package/dist/commands/action/run.js +8 -12
- package/dist/commands/agent/auth.d.ts +30 -0
- package/dist/commands/agent/auth.js +172 -0
- package/dist/commands/agent/discover.d.ts +9 -0
- package/dist/commands/agent/discover.js +67 -0
- package/dist/commands/agent/index.js +47 -12
- package/dist/commands/agent/list.d.ts +4 -1
- package/dist/commands/agent/list.js +78 -16
- package/dist/commands/agent/login.js +35 -31
- package/dist/commands/agent/restart.js +2 -0
- package/dist/commands/agent/shell.js +78 -19
- package/dist/commands/agent/staff/add.js +1 -12
- package/dist/commands/agent/staff/remove.js +9 -7
- package/dist/commands/agent/status.js +17 -4
- package/dist/commands/agent/temp/cleanup.js +7 -3
- package/dist/commands/agent/themes/index.js +4 -5
- package/dist/commands/agent/themes/list.js +5 -5
- package/dist/commands/agent/visit.js +17 -4
- package/dist/commands/branch/create.d.ts +4 -0
- package/dist/commands/branch/create.js +16 -8
- package/dist/commands/branch/index.js +1 -1
- package/dist/commands/branch/where.js +1 -0
- package/dist/commands/claude.d.ts +38 -0
- package/dist/commands/claude.js +899 -0
- package/dist/commands/commit.js +1 -1
- package/dist/commands/config/index.d.ts +12 -0
- package/dist/commands/config/index.js +271 -0
- package/dist/commands/docker/clean.js +2 -2
- package/dist/commands/docker/index.js +2 -2
- package/dist/commands/docker/list.js +3 -8
- package/dist/commands/docker/logs.js +2 -2
- package/dist/commands/docker/prune.js +1 -1
- package/dist/commands/docker/restart.js +2 -2
- package/dist/commands/docker/shell.js +2 -2
- package/dist/commands/docker/start.js +2 -2
- package/dist/commands/docker/status.js +1 -1
- package/dist/commands/docker/stop.js +2 -2
- package/dist/commands/docker/sync.js +2 -2
- package/dist/commands/epic/index.js +1 -1
- package/dist/commands/epic/link/index.js +25 -14
- package/dist/commands/epic/link/remove.js +2 -0
- package/dist/commands/epic/list.js +5 -5
- package/dist/commands/epic/progress.js +10 -4
- package/dist/commands/epic/spec.js +2 -0
- package/dist/commands/epic/ticket.js +3 -0
- package/dist/commands/execution/stop.js +1 -0
- package/dist/commands/init.js +4 -4
- package/dist/commands/project/index.js +1 -1
- package/dist/commands/project/spec.js +7 -0
- package/dist/commands/repo/add.js +1 -0
- package/dist/commands/repo/remove.js +1 -0
- package/dist/commands/roadmap/add-project.d.ts +18 -0
- package/dist/commands/roadmap/add-project.js +135 -0
- package/dist/commands/roadmap/create.d.ts +22 -0
- package/dist/commands/roadmap/create.js +156 -0
- package/dist/commands/roadmap/delete.d.ts +17 -0
- package/dist/commands/roadmap/delete.js +104 -0
- package/dist/commands/roadmap/generate.d.ts +22 -0
- package/dist/commands/roadmap/generate.js +201 -0
- package/dist/commands/roadmap/index.d.ts +13 -0
- package/dist/commands/roadmap/index.js +61 -0
- package/dist/commands/roadmap/list.d.ts +12 -0
- package/dist/commands/roadmap/list.js +42 -0
- package/dist/commands/roadmap/remove-project.d.ts +18 -0
- package/dist/commands/roadmap/remove-project.js +147 -0
- package/dist/commands/roadmap/reorder.d.ts +17 -0
- package/dist/commands/roadmap/reorder.js +157 -0
- package/dist/commands/roadmap/update.d.ts +19 -0
- package/dist/commands/roadmap/update.js +136 -0
- package/dist/commands/roadmap/view.d.ts +16 -0
- package/dist/commands/roadmap/view.js +103 -0
- package/dist/commands/spec/index.js +1 -1
- package/dist/commands/spec/link/index.js +24 -13
- package/dist/commands/spec/link/remove.js +2 -0
- package/dist/commands/status/index.js +1 -1
- package/dist/commands/status/list.js +0 -8
- package/dist/commands/template/delete.js +2 -0
- package/dist/commands/terminal/title.d.ts +12 -0
- package/dist/commands/terminal/title.js +48 -0
- package/dist/commands/ticket/complete.js +2 -0
- package/dist/commands/ticket/create.js +4 -2
- package/dist/commands/ticket/delete.js +2 -0
- package/dist/commands/ticket/edit.js +8 -2
- package/dist/commands/ticket/link/index.js +17 -3
- package/dist/commands/ticket/link/remove.js +2 -0
- package/dist/commands/ticket/list.js +1 -2
- package/dist/commands/ticket/move.js +2 -0
- package/dist/commands/ticket/project.js +3 -1
- package/dist/commands/ticket/reassign.js +2 -0
- package/dist/commands/ticket/spec.js +4 -2
- package/dist/commands/ticket/template/apply.js +4 -3
- package/dist/commands/ticket/template/create.js +2 -0
- package/dist/commands/ticket/template/index.js +1 -1
- package/dist/commands/ticket/update.js +2 -0
- package/dist/commands/work/index.js +1 -1
- package/dist/commands/work/revise.js +7 -1
- package/dist/commands/work/spawn.d.ts +2 -1
- package/dist/commands/work/spawn.js +131 -36
- package/dist/commands/work/start.d.ts +2 -1
- package/dist/commands/work/start.js +349 -69
- package/dist/commands/work/watch.js +10 -2
- package/dist/commands/workflow/create.js +3 -3
- package/dist/commands/workflow/switch.js +2 -1
- package/dist/commands/workspace/remove.js +0 -8
- package/dist/commands/workspace/use.js +1 -9
- package/dist/lib/agents/commands.js +18 -13
- package/dist/lib/database/index.d.ts +19 -12
- package/dist/lib/database/index.js +158 -42
- package/dist/lib/docker/resolve.js +1 -1
- package/dist/lib/execution/config.d.ts +6 -0
- package/dist/lib/execution/config.js +15 -2
- package/dist/lib/execution/devcontainer.d.ts +2 -0
- package/dist/lib/execution/devcontainer.js +41 -9
- package/dist/lib/execution/runners.d.ts +85 -3
- package/dist/lib/execution/runners.js +925 -228
- package/dist/lib/execution/spawner.d.ts +2 -2
- package/dist/lib/execution/spawner.js +4 -3
- package/dist/lib/execution/storage.d.ts +2 -1
- package/dist/lib/execution/storage.js +9 -13
- package/dist/lib/execution/types.d.ts +10 -1
- package/dist/lib/execution/types.js +3 -1
- package/dist/lib/init/index.js +1 -0
- package/dist/lib/machine-config.js +1 -1
- package/dist/lib/pmo/base-command.js +5 -9
- package/dist/lib/pmo/index.js +2 -0
- package/dist/lib/pmo/schema.d.ts +6 -0
- package/dist/lib/pmo/schema.js +36 -0
- package/dist/lib/pmo/storage/base.js +3 -3
- package/dist/lib/pmo/storage/index.d.ts +16 -1
- package/dist/lib/pmo/storage/index.js +45 -0
- package/dist/lib/pmo/storage/roadmaps.d.ts +62 -0
- package/dist/lib/pmo/storage/roadmaps.js +301 -0
- package/dist/lib/pmo/storage/specs.js +2 -0
- package/dist/lib/pmo/storage/types.d.ts +14 -0
- package/dist/lib/pmo/sync-manager.d.ts +1 -1
- package/dist/lib/pmo/sync-manager.js +1 -1
- package/dist/lib/pmo/types.d.ts +41 -0
- package/dist/lib/pmo/utils.d.ts +2 -0
- package/dist/lib/pmo/utils.js +22 -1
- package/dist/lib/repos/index.js +7 -1
- package/dist/lib/terminal.d.ts +31 -0
- package/dist/lib/terminal.js +48 -0
- package/dist/lib/themes.d.ts +21 -3
- package/dist/lib/themes.js +80 -23
- package/dist/lib/workspace-config.d.ts +80 -0
- package/dist/lib/workspace-config.js +100 -0
- package/oclif.manifest.json +4065 -3225
- package/package.json +10 -6
- package/LICENSE +0 -21
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
export default class RoadmapUpdate extends PMOCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
id: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
default: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
};
|
|
15
|
+
protected getPMOOptions(): {
|
|
16
|
+
promptIfMultiple: boolean;
|
|
17
|
+
};
|
|
18
|
+
execute(): Promise<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
4
|
+
import { styles } from '../../lib/styles.js';
|
|
5
|
+
import { shouldOutputJson, outputPromptAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, buildFormPromptConfig, } from '../../lib/prompt-json.js';
|
|
6
|
+
export default class RoadmapUpdate extends PMOCommand {
|
|
7
|
+
static description = 'Update a roadmap';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> <%= command.id %> my-roadmap --name "New Name"',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> my-roadmap --default',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> # Interactive selection',
|
|
12
|
+
];
|
|
13
|
+
static args = {
|
|
14
|
+
id: Args.string({
|
|
15
|
+
description: 'Roadmap ID to update',
|
|
16
|
+
required: false,
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
19
|
+
static flags = {
|
|
20
|
+
...pmoBaseFlags,
|
|
21
|
+
name: Flags.string({
|
|
22
|
+
char: 'n',
|
|
23
|
+
description: 'New roadmap name',
|
|
24
|
+
}),
|
|
25
|
+
description: Flags.string({
|
|
26
|
+
char: 'd',
|
|
27
|
+
description: 'New roadmap description',
|
|
28
|
+
}),
|
|
29
|
+
default: Flags.boolean({
|
|
30
|
+
description: 'Set as the default roadmap',
|
|
31
|
+
allowNo: true,
|
|
32
|
+
}),
|
|
33
|
+
json: Flags.boolean({
|
|
34
|
+
description: 'Output prompt configuration as JSON (for AI agents/scripts)',
|
|
35
|
+
default: false,
|
|
36
|
+
}),
|
|
37
|
+
};
|
|
38
|
+
getPMOOptions() {
|
|
39
|
+
return { promptIfMultiple: false };
|
|
40
|
+
}
|
|
41
|
+
async execute() {
|
|
42
|
+
const { args, flags } = await this.parse(RoadmapUpdate);
|
|
43
|
+
const jsonMode = shouldOutputJson(flags);
|
|
44
|
+
let roadmapId = args.id;
|
|
45
|
+
if (!roadmapId) {
|
|
46
|
+
const roadmaps = await this.storage.listRoadmaps();
|
|
47
|
+
if (roadmaps.length === 0) {
|
|
48
|
+
if (jsonMode) {
|
|
49
|
+
outputErrorAsJson('NO_ROADMAPS', 'No roadmaps found', createMetadata('roadmap update', flags));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
this.error('No roadmaps found. Create one with: prlt roadmap create');
|
|
53
|
+
}
|
|
54
|
+
if (jsonMode) {
|
|
55
|
+
const choices = roadmaps.map(r => ({
|
|
56
|
+
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
57
|
+
value: r.id,
|
|
58
|
+
}));
|
|
59
|
+
outputPromptAsJson(buildPromptConfig('list', 'id', 'Select roadmap to update:', choices), createMetadata('roadmap update', flags));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const { selected } = await inquirer.prompt([{
|
|
63
|
+
type: 'list',
|
|
64
|
+
name: 'selected',
|
|
65
|
+
message: 'Select roadmap to update:',
|
|
66
|
+
choices: roadmaps.map(r => ({
|
|
67
|
+
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
68
|
+
value: r.id,
|
|
69
|
+
})),
|
|
70
|
+
}]);
|
|
71
|
+
roadmapId = selected;
|
|
72
|
+
}
|
|
73
|
+
const roadmap = await this.storage.getRoadmap(roadmapId);
|
|
74
|
+
if (!roadmap) {
|
|
75
|
+
if (jsonMode) {
|
|
76
|
+
outputErrorAsJson('NOT_FOUND', `Roadmap not found: ${roadmapId}`, createMetadata('roadmap update', flags));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
this.error(`Roadmap not found: ${roadmapId}`);
|
|
80
|
+
}
|
|
81
|
+
// If no flags provided, prompt for updates
|
|
82
|
+
const hasUpdateFlags = flags.name !== undefined || flags.description !== undefined || flags.default !== undefined;
|
|
83
|
+
if (!hasUpdateFlags) {
|
|
84
|
+
const fields = [
|
|
85
|
+
{ type: 'input', name: 'name', message: 'New name (leave blank to keep current):', default: roadmap.name },
|
|
86
|
+
{ type: 'input', name: 'description', message: 'New description (leave blank to keep current):', default: roadmap.description || '' },
|
|
87
|
+
{
|
|
88
|
+
type: 'list',
|
|
89
|
+
name: 'isDefault',
|
|
90
|
+
message: 'Set as default roadmap?',
|
|
91
|
+
choices: [
|
|
92
|
+
{ name: 'No change', value: 'no-change' },
|
|
93
|
+
{ name: 'Yes', value: 'yes' },
|
|
94
|
+
{ name: 'No', value: 'no' },
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
if (jsonMode) {
|
|
99
|
+
outputPromptAsJson(buildFormPromptConfig(fields), createMetadata('roadmap update', flags));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const answers = await inquirer.prompt(fields);
|
|
103
|
+
const changes = {};
|
|
104
|
+
if (answers.name && answers.name !== roadmap.name) {
|
|
105
|
+
changes.name = answers.name;
|
|
106
|
+
}
|
|
107
|
+
if (answers.description !== (roadmap.description || '')) {
|
|
108
|
+
changes.description = answers.description || undefined;
|
|
109
|
+
}
|
|
110
|
+
if (answers.isDefault === 'yes') {
|
|
111
|
+
changes.isDefault = true;
|
|
112
|
+
}
|
|
113
|
+
else if (answers.isDefault === 'no') {
|
|
114
|
+
changes.isDefault = false;
|
|
115
|
+
}
|
|
116
|
+
if (Object.keys(changes).length === 0) {
|
|
117
|
+
this.log(styles.muted('No changes made.'));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const updated = await this.storage.updateRoadmap(roadmapId, changes);
|
|
121
|
+
this.log(styles.success(`Updated roadmap "${updated.name}"`));
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
// Use flags
|
|
125
|
+
const changes = {};
|
|
126
|
+
if (flags.name !== undefined)
|
|
127
|
+
changes.name = flags.name;
|
|
128
|
+
if (flags.description !== undefined)
|
|
129
|
+
changes.description = flags.description;
|
|
130
|
+
if (flags.default !== undefined)
|
|
131
|
+
changes.isDefault = flags.default;
|
|
132
|
+
const updated = await this.storage.updateRoadmap(roadmapId, changes);
|
|
133
|
+
this.log(styles.success(`Updated roadmap "${updated.name}"`));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
export default class RoadmapView extends PMOCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
id: import("@oclif/core/interfaces").Arg<string | undefined, 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
|
+
protected getPMOOptions(): {
|
|
13
|
+
promptIfMultiple: boolean;
|
|
14
|
+
};
|
|
15
|
+
execute(): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
4
|
+
import { styles } from '../../lib/styles.js';
|
|
5
|
+
import { shouldOutputJson, outputPromptAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
|
|
6
|
+
export default class RoadmapView extends PMOCommand {
|
|
7
|
+
static description = 'View roadmap details and its projects';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> <%= command.id %> my-roadmap',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> # Interactive selection',
|
|
11
|
+
];
|
|
12
|
+
static args = {
|
|
13
|
+
id: Args.string({
|
|
14
|
+
description: 'Roadmap ID to view',
|
|
15
|
+
required: false,
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
static flags = {
|
|
19
|
+
...pmoBaseFlags,
|
|
20
|
+
json: Flags.boolean({
|
|
21
|
+
description: 'Output prompt configuration as JSON (for AI agents/scripts)',
|
|
22
|
+
default: false,
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
getPMOOptions() {
|
|
26
|
+
return { promptIfMultiple: false };
|
|
27
|
+
}
|
|
28
|
+
async execute() {
|
|
29
|
+
const { args, flags } = await this.parse(RoadmapView);
|
|
30
|
+
const jsonMode = shouldOutputJson(flags);
|
|
31
|
+
let roadmapId = args.id;
|
|
32
|
+
if (!roadmapId) {
|
|
33
|
+
const roadmaps = await this.storage.listRoadmaps();
|
|
34
|
+
if (roadmaps.length === 0) {
|
|
35
|
+
if (jsonMode) {
|
|
36
|
+
outputErrorAsJson('NO_ROADMAPS', 'No roadmaps found', createMetadata('roadmap view', flags));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this.error('No roadmaps found. Create one with: prlt roadmap create');
|
|
40
|
+
}
|
|
41
|
+
if (jsonMode) {
|
|
42
|
+
const choices = roadmaps.map(r => ({
|
|
43
|
+
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
44
|
+
value: r.id,
|
|
45
|
+
}));
|
|
46
|
+
outputPromptAsJson(buildPromptConfig('list', 'id', 'Select roadmap to view:', choices), createMetadata('roadmap view', flags));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const { selected } = await inquirer.prompt([{
|
|
50
|
+
type: 'list',
|
|
51
|
+
name: 'selected',
|
|
52
|
+
message: 'Select roadmap to view:',
|
|
53
|
+
choices: roadmaps.map(r => ({
|
|
54
|
+
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
55
|
+
value: r.id,
|
|
56
|
+
})),
|
|
57
|
+
}]);
|
|
58
|
+
roadmapId = selected;
|
|
59
|
+
}
|
|
60
|
+
const roadmap = await this.storage.getRoadmap(roadmapId);
|
|
61
|
+
if (!roadmap) {
|
|
62
|
+
if (jsonMode) {
|
|
63
|
+
outputErrorAsJson('NOT_FOUND', `Roadmap not found: ${roadmapId}`, createMetadata('roadmap view', flags));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
this.error(`Roadmap not found: ${roadmapId}`);
|
|
67
|
+
}
|
|
68
|
+
// Display roadmap details
|
|
69
|
+
this.log(styles.title(`\n${roadmap.name}`));
|
|
70
|
+
if (roadmap.isDefault) {
|
|
71
|
+
this.log(styles.success(' (default roadmap)'));
|
|
72
|
+
}
|
|
73
|
+
this.log(styles.muted(` ID: ${roadmap.id}`));
|
|
74
|
+
if (roadmap.description) {
|
|
75
|
+
this.log(styles.muted(` ${roadmap.description}`));
|
|
76
|
+
}
|
|
77
|
+
this.log('');
|
|
78
|
+
// Get projects in this roadmap
|
|
79
|
+
const projects = await this.storage.listRoadmapProjects(roadmap.id);
|
|
80
|
+
if (projects.length === 0) {
|
|
81
|
+
this.log(styles.muted('No projects in this roadmap.'));
|
|
82
|
+
this.log(styles.muted(`Add projects with: prlt roadmap add-project ${roadmap.id}`));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
this.log(styles.emphasis('Projects (in order):'));
|
|
86
|
+
this.log('');
|
|
87
|
+
for (let i = 0; i < projects.length; i++) {
|
|
88
|
+
const project = projects[i];
|
|
89
|
+
// eslint-disable-next-line no-await-in-loop -- Sequential display output
|
|
90
|
+
const tickets = await this.storage.listTickets(project.id);
|
|
91
|
+
const ticketCount = tickets.length;
|
|
92
|
+
this.log(` ${i + 1}. ${styles.emphasis(project.name)}`);
|
|
93
|
+
this.log(styles.muted(` ID: ${project.id}`));
|
|
94
|
+
this.log(styles.muted(` Tickets: ${ticketCount}`));
|
|
95
|
+
if (project.description) {
|
|
96
|
+
this.log(styles.muted(` ${project.description}`));
|
|
97
|
+
}
|
|
98
|
+
this.log('');
|
|
99
|
+
}
|
|
100
|
+
this.log(styles.muted(`Generate markdown: prlt roadmap generate ${roadmap.id}`));
|
|
101
|
+
this.log(styles.muted(`Reorder projects: prlt roadmap reorder ${roadmap.id}`));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
3
|
-
import { shouldOutputJson
|
|
3
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
4
4
|
export default class Spec extends PMOCommand {
|
|
5
5
|
static description = 'Interactive menu for spec operations';
|
|
6
6
|
static examples = [
|
|
@@ -69,8 +69,10 @@ export default class SpecLink extends PMOCommand {
|
|
|
69
69
|
// Interactive mode: show menu in a loop
|
|
70
70
|
let continueLoop = true;
|
|
71
71
|
while (continueLoop) {
|
|
72
|
+
// eslint-disable-next-line no-await-in-loop -- Interactive user loop
|
|
72
73
|
const allSpecs = await this.storage.listSpecs();
|
|
73
74
|
const otherSpecs = allSpecs.filter(s => s.id !== specId);
|
|
75
|
+
// eslint-disable-next-line no-await-in-loop -- Interactive user prompt
|
|
74
76
|
const { action } = await inquirer.prompt([{
|
|
75
77
|
type: 'list',
|
|
76
78
|
name: 'action',
|
|
@@ -90,15 +92,18 @@ export default class SpecLink extends PMOCommand {
|
|
|
90
92
|
continue;
|
|
91
93
|
}
|
|
92
94
|
if (action === 'view') {
|
|
95
|
+
// eslint-disable-next-line no-await-in-loop -- User action handling
|
|
93
96
|
await this.viewDependencies(specId, spec, flags.all);
|
|
94
97
|
continue;
|
|
95
98
|
}
|
|
96
99
|
if (action === 'remove') {
|
|
100
|
+
// eslint-disable-next-line no-await-in-loop -- User action handling
|
|
97
101
|
const dependencies = await this.storage.listSpecDependencies(specId);
|
|
98
102
|
if (dependencies.length === 0) {
|
|
99
103
|
this.log(styles.muted('\nNo dependencies to remove.'));
|
|
100
104
|
continue;
|
|
101
105
|
}
|
|
106
|
+
// eslint-disable-next-line no-await-in-loop -- Building choices for current interaction
|
|
102
107
|
const choices = await Promise.all(dependencies.map(async (dep) => {
|
|
103
108
|
const depSpec = await this.storage.getSpec(dep.dependsOnSpecId);
|
|
104
109
|
return {
|
|
@@ -106,12 +111,14 @@ export default class SpecLink extends PMOCommand {
|
|
|
106
111
|
value: { targetId: dep.dependsOnSpecId, type: dep.dependencyType }
|
|
107
112
|
};
|
|
108
113
|
}));
|
|
114
|
+
// eslint-disable-next-line no-await-in-loop -- User selection prompt
|
|
109
115
|
const { selected } = await inquirer.prompt([{
|
|
110
116
|
type: 'list',
|
|
111
117
|
name: 'selected',
|
|
112
118
|
message: 'Select dependency to remove:',
|
|
113
119
|
choices,
|
|
114
120
|
}]);
|
|
121
|
+
// eslint-disable-next-line no-await-in-loop -- Action after user selection
|
|
115
122
|
await this.storage.deleteSpecDependency(specId, selected.targetId, selected.type);
|
|
116
123
|
this.log(styles.success(`\n✅ Removed dependency: ${specId} → ${selected.targetId}`));
|
|
117
124
|
continue;
|
|
@@ -121,12 +128,14 @@ export default class SpecLink extends PMOCommand {
|
|
|
121
128
|
this.log(styles.muted('\nNo other specs to link to.'));
|
|
122
129
|
continue;
|
|
123
130
|
}
|
|
131
|
+
// eslint-disable-next-line no-await-in-loop -- User selection prompt
|
|
124
132
|
const { targetId } = await inquirer.prompt([{
|
|
125
133
|
type: 'list',
|
|
126
134
|
name: 'targetId',
|
|
127
135
|
message: `Select spec that ${specId} ${action === 'depends_on' ? 'depends on' : action === 'relates_to' ? 'relates to' : 'duplicates'}:`,
|
|
128
136
|
choices: otherSpecs.map(s => ({ name: `${s.id} - ${s.title}`, value: s.id })),
|
|
129
137
|
}]);
|
|
138
|
+
// eslint-disable-next-line no-await-in-loop -- Action after user selection
|
|
130
139
|
await this.addDependency(specId, targetId, action, spec.title);
|
|
131
140
|
}
|
|
132
141
|
}
|
|
@@ -161,8 +170,9 @@ export default class SpecLink extends PMOCommand {
|
|
|
161
170
|
const dependsOn = dependencies.filter(d => d.dependencyType === 'depends_on');
|
|
162
171
|
if (dependsOn.length > 0) {
|
|
163
172
|
this.log(styles.muted('\n Depends on:'));
|
|
164
|
-
|
|
165
|
-
|
|
173
|
+
// Fetch all dependency specs in parallel
|
|
174
|
+
const depSpecs = await Promise.all(dependsOn.map(dep => this.storage.getSpec(dep.dependsOnSpecId)));
|
|
175
|
+
for (const depSpec of depSpecs) {
|
|
166
176
|
if (depSpec)
|
|
167
177
|
this.log(` - ${depSpec.id}: ${depSpec.title}`);
|
|
168
178
|
}
|
|
@@ -170,27 +180,28 @@ export default class SpecLink extends PMOCommand {
|
|
|
170
180
|
const otherDeps = dependencies.filter(d => d.dependencyType !== 'depends_on');
|
|
171
181
|
if (otherDeps.length > 0) {
|
|
172
182
|
this.log(styles.muted('\n Related:'));
|
|
173
|
-
|
|
174
|
-
|
|
183
|
+
// Fetch all related specs in parallel
|
|
184
|
+
const relatedSpecs = await Promise.all(otherDeps.map(async (dep) => ({ dep, spec: await this.storage.getSpec(dep.dependsOnSpecId) })));
|
|
185
|
+
for (const { dep, spec: relatedSpec } of relatedSpecs) {
|
|
175
186
|
if (relatedSpec)
|
|
176
187
|
this.log(` - ${dep.dependencyType}: ${relatedSpec.id} - ${relatedSpec.title}`);
|
|
177
188
|
}
|
|
178
189
|
}
|
|
179
190
|
if (showAll) {
|
|
180
191
|
const allSpecs = await this.storage.listSpecs();
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
192
|
+
// Find all specs that depend on this spec in parallel
|
|
193
|
+
const dependedByResults = await Promise.all(allSpecs
|
|
194
|
+
.filter(otherSpec => otherSpec.id !== specId)
|
|
195
|
+
.map(async (otherSpec) => {
|
|
185
196
|
const otherDeps = await this.storage.listSpecDependencies(otherSpec.id);
|
|
186
197
|
const dep = otherDeps.find(d => d.dependsOnSpecId === specId);
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
198
|
+
return dep ? { spec: otherSpec, type: dep.dependencyType } : null;
|
|
199
|
+
}));
|
|
200
|
+
const dependedBy = dependedByResults.filter((d) => d !== null);
|
|
190
201
|
if (dependedBy.length > 0) {
|
|
191
202
|
this.log(styles.muted('\n Depended by:'));
|
|
192
|
-
for (const
|
|
193
|
-
this.log(` - ${
|
|
203
|
+
for (const item of dependedBy)
|
|
204
|
+
this.log(` - ${item.spec.id}: ${item.spec.title} (${item.type})`);
|
|
194
205
|
}
|
|
195
206
|
}
|
|
196
207
|
if (dependencies.length === 0)
|
|
@@ -69,6 +69,8 @@ export default class SpecLinkRemove extends PMOCommand {
|
|
|
69
69
|
this.log(styles.muted('\nCancelled.'));
|
|
70
70
|
return;
|
|
71
71
|
}
|
|
72
|
+
// Delete sequentially for data integrity
|
|
73
|
+
// eslint-disable-next-line no-await-in-loop
|
|
72
74
|
for (const dep of dependencies)
|
|
73
75
|
await this.storage.deleteSpecDependency(args.id, dep.dependsOnSpecId, dep.dependencyType);
|
|
74
76
|
this.log(styles.success(`\n✅ Removed ${dependencies.length} dependencies from ${args.id}`));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import inquirer from 'inquirer';
|
|
3
3
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
4
|
-
import { shouldOutputJson
|
|
4
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
5
5
|
export default class Status extends PMOCommand {
|
|
6
6
|
static description = 'Interactive menu for workflow status operations';
|
|
7
7
|
static aliases = ['statuses'];
|
|
@@ -53,14 +53,6 @@ export default class StatusList extends PMOCommand {
|
|
|
53
53
|
completed: '✅',
|
|
54
54
|
canceled: '🚫',
|
|
55
55
|
};
|
|
56
|
-
const categoryColors = {
|
|
57
|
-
triage: '#A78BFA', // purple
|
|
58
|
-
backlog: '#9CA3AF', // gray
|
|
59
|
-
unstarted: '#60A5FA', // blue
|
|
60
|
-
started: '#FBBF24', // yellow
|
|
61
|
-
completed: '#34D399', // green
|
|
62
|
-
canceled: '#F87171', // red
|
|
63
|
-
};
|
|
64
56
|
for (const category of STATE_CATEGORY_ORDER) {
|
|
65
57
|
if (flags.category && flags.category !== category)
|
|
66
58
|
continue;
|
|
@@ -125,9 +125,11 @@ export default class TemplateDelete extends PMOCommand {
|
|
|
125
125
|
try {
|
|
126
126
|
switch (templateType) {
|
|
127
127
|
case 'ticket':
|
|
128
|
+
// eslint-disable-next-line no-await-in-loop -- Sequential deletes with error handling
|
|
128
129
|
await this.storage.deleteTicketTemplate(id);
|
|
129
130
|
break;
|
|
130
131
|
case 'phase':
|
|
132
|
+
// eslint-disable-next-line no-await-in-loop -- Sequential deletes with error handling
|
|
131
133
|
await this.storage.deletePhaseTemplate(id);
|
|
132
134
|
break;
|
|
133
135
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class TerminalTitle extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
title: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
reset: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { setTerminalTitle, resetTerminalTitle } from '../../lib/terminal.js';
|
|
4
|
+
import { styles } from '../../lib/styles.js';
|
|
5
|
+
export default class TerminalTitle extends Command {
|
|
6
|
+
static description = 'Set the terminal tab/window title';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> <%= command.id %> "My Custom Name"',
|
|
9
|
+
'<%= config.bin %> <%= command.id %> # Interactive prompt',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> --reset',
|
|
11
|
+
];
|
|
12
|
+
static args = {
|
|
13
|
+
title: Args.string({
|
|
14
|
+
description: 'Title to set for the terminal tab/window',
|
|
15
|
+
required: false,
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
static flags = {
|
|
19
|
+
reset: Flags.boolean({
|
|
20
|
+
char: 'r',
|
|
21
|
+
description: 'Reset terminal title to default',
|
|
22
|
+
default: false,
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
async run() {
|
|
26
|
+
const { args, flags } = await this.parse(TerminalTitle);
|
|
27
|
+
// Handle reset flag
|
|
28
|
+
if (flags.reset) {
|
|
29
|
+
resetTerminalTitle();
|
|
30
|
+
this.log(styles.success('Terminal title reset to default'));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// Get title from args or prompt
|
|
34
|
+
let title = args.title;
|
|
35
|
+
if (!title) {
|
|
36
|
+
const response = await inquirer.prompt([{
|
|
37
|
+
type: 'input',
|
|
38
|
+
name: 'title',
|
|
39
|
+
message: 'Enter terminal title:',
|
|
40
|
+
validate: (input) => input.length > 0 || 'Title cannot be empty',
|
|
41
|
+
}]);
|
|
42
|
+
title = response.title;
|
|
43
|
+
}
|
|
44
|
+
// Set the title
|
|
45
|
+
setTerminalTitle(title);
|
|
46
|
+
this.log(styles.success(`Terminal title set to "${title}"`));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -141,12 +141,14 @@ export default class TicketComplete extends PMOCommand {
|
|
|
141
141
|
// Complete each ticket
|
|
142
142
|
let successCount = 0;
|
|
143
143
|
let failCount = 0;
|
|
144
|
+
// Process sequentially for clear success/failure logging
|
|
144
145
|
for (const ticketId of selectedTickets) {
|
|
145
146
|
try {
|
|
146
147
|
const ticket = incompleteTickets.find(t => t.id === ticketId);
|
|
147
148
|
if (!ticket) {
|
|
148
149
|
throw new Error('Ticket not found in incomplete tickets list');
|
|
149
150
|
}
|
|
151
|
+
// eslint-disable-next-line no-await-in-loop
|
|
150
152
|
await this.storage.moveTicket(ticket.projectId, ticketId, doneColumnName);
|
|
151
153
|
this.log(styles.success(`Completed ${ticketId}`));
|
|
152
154
|
successCount++;
|
|
@@ -116,7 +116,7 @@ export default class TicketCreate extends PMOCommand {
|
|
|
116
116
|
}
|
|
117
117
|
// Parse labels from flag
|
|
118
118
|
const labelsFromFlag = flags.labels
|
|
119
|
-
? flags.labels.split(',').map(l => l.trim()).filter(
|
|
119
|
+
? flags.labels.split(',').map(l => l.trim()).filter(Boolean)
|
|
120
120
|
: undefined;
|
|
121
121
|
// Get ticket data (interactive or from flags)
|
|
122
122
|
let ticketData;
|
|
@@ -172,7 +172,9 @@ export default class TicketCreate extends PMOCommand {
|
|
|
172
172
|
});
|
|
173
173
|
// Add subtasks from template if applicable
|
|
174
174
|
if (template && template.suggestedSubtasks.length > 0) {
|
|
175
|
+
// Sequential subtask creation for consistent ordering
|
|
175
176
|
for (const subtask of template.suggestedSubtasks) {
|
|
177
|
+
// eslint-disable-next-line no-await-in-loop
|
|
176
178
|
await this.storage.addSubtask(ticket.id, subtask.title);
|
|
177
179
|
}
|
|
178
180
|
}
|
|
@@ -314,7 +316,7 @@ export default class TicketCreate extends PMOCommand {
|
|
|
314
316
|
const description = await this.promptStructuredDescription(flags.description || template?.descriptionTemplate);
|
|
315
317
|
// Parse labels from flag or use template defaults
|
|
316
318
|
const labels = flags.labels
|
|
317
|
-
? flags.labels.split(',').map(l => l.trim()).filter(
|
|
319
|
+
? flags.labels.split(',').map(l => l.trim()).filter(Boolean)
|
|
318
320
|
: template?.defaultLabels;
|
|
319
321
|
return {
|
|
320
322
|
title: answers.title,
|
|
@@ -153,8 +153,10 @@ export default class TicketDelete extends PMOCommand {
|
|
|
153
153
|
// Delete each ticket
|
|
154
154
|
let successCount = 0;
|
|
155
155
|
let failCount = 0;
|
|
156
|
+
// Process sequentially for clear success/failure logging
|
|
156
157
|
for (const ticketId of selectedTickets) {
|
|
157
158
|
try {
|
|
159
|
+
// eslint-disable-next-line no-await-in-loop
|
|
158
160
|
await this.storage.deleteTicket(ticketId);
|
|
159
161
|
this.log(styles.success(`Deleted ${ticketId}`));
|
|
160
162
|
successCount++;
|
|
@@ -138,7 +138,9 @@ export default class TicketEdit extends PMOCommand {
|
|
|
138
138
|
if (flags.description)
|
|
139
139
|
updates.description = flags.description;
|
|
140
140
|
if (flags.priority) {
|
|
141
|
-
|
|
141
|
+
// 'none' clears the priority (sets to null in database), otherwise use the flag value
|
|
142
|
+
// Type assertion needed because Ticket interface uses string | undefined, but storage accepts null
|
|
143
|
+
updates.priority = flags.priority === 'none' ? null : flags.priority;
|
|
142
144
|
}
|
|
143
145
|
if (flags.category)
|
|
144
146
|
updates.category = flags.category;
|
|
@@ -147,17 +149,19 @@ export default class TicketEdit extends PMOCommand {
|
|
|
147
149
|
if (flags.assignee)
|
|
148
150
|
updates.assignee = flags.assignee;
|
|
149
151
|
}
|
|
150
|
-
// Handle subtasks
|
|
152
|
+
// Handle subtasks - sequential for consistent ordering
|
|
151
153
|
let subtasksChanged = false;
|
|
152
154
|
if (flags['clear-subtasks']) {
|
|
153
155
|
// Clear all subtasks first - get from ticket object
|
|
154
156
|
for (const subtask of ticket.subtasks) {
|
|
157
|
+
// eslint-disable-next-line no-await-in-loop
|
|
155
158
|
await this.storage.removeSubtask(ticketId, subtask.id);
|
|
156
159
|
}
|
|
157
160
|
subtasksChanged = true;
|
|
158
161
|
}
|
|
159
162
|
if (flags['add-subtask'] && flags['add-subtask'].length > 0) {
|
|
160
163
|
for (const subtaskTitle of flags['add-subtask']) {
|
|
164
|
+
// eslint-disable-next-line no-await-in-loop
|
|
161
165
|
await this.storage.addSubtask(ticketId, subtaskTitle);
|
|
162
166
|
}
|
|
163
167
|
subtasksChanged = true;
|
|
@@ -184,7 +188,9 @@ export default class TicketEdit extends PMOCommand {
|
|
|
184
188
|
acChanged = true;
|
|
185
189
|
}
|
|
186
190
|
if (flags['add-ac'] && flags['add-ac'].length > 0) {
|
|
191
|
+
// Sequential for consistent ordering
|
|
187
192
|
for (const criterion of flags['add-ac']) {
|
|
193
|
+
// eslint-disable-next-line no-await-in-loop
|
|
188
194
|
await this.storage.addAcceptanceCriterion(ticketId, criterion);
|
|
189
195
|
}
|
|
190
196
|
acChanged = true;
|