@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.
Files changed (152) hide show
  1. package/README.md +25 -0
  2. package/bin/dev.js +0 -0
  3. package/dist/commands/action/index.js +1 -1
  4. package/dist/commands/action/run.js +8 -12
  5. package/dist/commands/agent/auth.d.ts +30 -0
  6. package/dist/commands/agent/auth.js +172 -0
  7. package/dist/commands/agent/discover.d.ts +9 -0
  8. package/dist/commands/agent/discover.js +67 -0
  9. package/dist/commands/agent/index.js +47 -12
  10. package/dist/commands/agent/list.d.ts +4 -1
  11. package/dist/commands/agent/list.js +78 -16
  12. package/dist/commands/agent/login.js +35 -31
  13. package/dist/commands/agent/restart.js +2 -0
  14. package/dist/commands/agent/shell.js +78 -19
  15. package/dist/commands/agent/staff/add.js +1 -12
  16. package/dist/commands/agent/staff/remove.js +9 -7
  17. package/dist/commands/agent/status.js +17 -4
  18. package/dist/commands/agent/temp/cleanup.js +7 -3
  19. package/dist/commands/agent/themes/index.js +4 -5
  20. package/dist/commands/agent/themes/list.js +5 -5
  21. package/dist/commands/agent/visit.js +17 -4
  22. package/dist/commands/branch/create.d.ts +4 -0
  23. package/dist/commands/branch/create.js +16 -8
  24. package/dist/commands/branch/index.js +1 -1
  25. package/dist/commands/branch/where.js +1 -0
  26. package/dist/commands/claude.d.ts +38 -0
  27. package/dist/commands/claude.js +899 -0
  28. package/dist/commands/commit.js +1 -1
  29. package/dist/commands/config/index.d.ts +12 -0
  30. package/dist/commands/config/index.js +271 -0
  31. package/dist/commands/docker/clean.js +2 -2
  32. package/dist/commands/docker/index.js +2 -2
  33. package/dist/commands/docker/list.js +3 -8
  34. package/dist/commands/docker/logs.js +2 -2
  35. package/dist/commands/docker/prune.js +1 -1
  36. package/dist/commands/docker/restart.js +2 -2
  37. package/dist/commands/docker/shell.js +2 -2
  38. package/dist/commands/docker/start.js +2 -2
  39. package/dist/commands/docker/status.js +1 -1
  40. package/dist/commands/docker/stop.js +2 -2
  41. package/dist/commands/docker/sync.js +2 -2
  42. package/dist/commands/epic/index.js +1 -1
  43. package/dist/commands/epic/link/index.js +25 -14
  44. package/dist/commands/epic/link/remove.js +2 -0
  45. package/dist/commands/epic/list.js +5 -5
  46. package/dist/commands/epic/progress.js +10 -4
  47. package/dist/commands/epic/spec.js +2 -0
  48. package/dist/commands/epic/ticket.js +3 -0
  49. package/dist/commands/execution/stop.js +1 -0
  50. package/dist/commands/init.js +4 -4
  51. package/dist/commands/project/index.js +1 -1
  52. package/dist/commands/project/spec.js +7 -0
  53. package/dist/commands/repo/add.js +1 -0
  54. package/dist/commands/repo/remove.js +1 -0
  55. package/dist/commands/roadmap/add-project.d.ts +18 -0
  56. package/dist/commands/roadmap/add-project.js +135 -0
  57. package/dist/commands/roadmap/create.d.ts +22 -0
  58. package/dist/commands/roadmap/create.js +156 -0
  59. package/dist/commands/roadmap/delete.d.ts +17 -0
  60. package/dist/commands/roadmap/delete.js +104 -0
  61. package/dist/commands/roadmap/generate.d.ts +22 -0
  62. package/dist/commands/roadmap/generate.js +201 -0
  63. package/dist/commands/roadmap/index.d.ts +13 -0
  64. package/dist/commands/roadmap/index.js +61 -0
  65. package/dist/commands/roadmap/list.d.ts +12 -0
  66. package/dist/commands/roadmap/list.js +42 -0
  67. package/dist/commands/roadmap/remove-project.d.ts +18 -0
  68. package/dist/commands/roadmap/remove-project.js +147 -0
  69. package/dist/commands/roadmap/reorder.d.ts +17 -0
  70. package/dist/commands/roadmap/reorder.js +157 -0
  71. package/dist/commands/roadmap/update.d.ts +19 -0
  72. package/dist/commands/roadmap/update.js +136 -0
  73. package/dist/commands/roadmap/view.d.ts +16 -0
  74. package/dist/commands/roadmap/view.js +103 -0
  75. package/dist/commands/spec/index.js +1 -1
  76. package/dist/commands/spec/link/index.js +24 -13
  77. package/dist/commands/spec/link/remove.js +2 -0
  78. package/dist/commands/status/index.js +1 -1
  79. package/dist/commands/status/list.js +0 -8
  80. package/dist/commands/template/delete.js +2 -0
  81. package/dist/commands/terminal/title.d.ts +12 -0
  82. package/dist/commands/terminal/title.js +48 -0
  83. package/dist/commands/ticket/complete.js +2 -0
  84. package/dist/commands/ticket/create.js +4 -2
  85. package/dist/commands/ticket/delete.js +2 -0
  86. package/dist/commands/ticket/edit.js +8 -2
  87. package/dist/commands/ticket/link/index.js +17 -3
  88. package/dist/commands/ticket/link/remove.js +2 -0
  89. package/dist/commands/ticket/list.js +1 -2
  90. package/dist/commands/ticket/move.js +2 -0
  91. package/dist/commands/ticket/project.js +3 -1
  92. package/dist/commands/ticket/reassign.js +2 -0
  93. package/dist/commands/ticket/spec.js +4 -2
  94. package/dist/commands/ticket/template/apply.js +4 -3
  95. package/dist/commands/ticket/template/create.js +2 -0
  96. package/dist/commands/ticket/template/index.js +1 -1
  97. package/dist/commands/ticket/update.js +2 -0
  98. package/dist/commands/work/index.js +1 -1
  99. package/dist/commands/work/revise.js +7 -1
  100. package/dist/commands/work/spawn.d.ts +2 -1
  101. package/dist/commands/work/spawn.js +131 -36
  102. package/dist/commands/work/start.d.ts +2 -1
  103. package/dist/commands/work/start.js +349 -69
  104. package/dist/commands/work/watch.js +10 -2
  105. package/dist/commands/workflow/create.js +3 -3
  106. package/dist/commands/workflow/switch.js +2 -1
  107. package/dist/commands/workspace/remove.js +0 -8
  108. package/dist/commands/workspace/use.js +1 -9
  109. package/dist/lib/agents/commands.js +18 -13
  110. package/dist/lib/database/index.d.ts +19 -12
  111. package/dist/lib/database/index.js +158 -42
  112. package/dist/lib/docker/resolve.js +1 -1
  113. package/dist/lib/execution/config.d.ts +6 -0
  114. package/dist/lib/execution/config.js +15 -2
  115. package/dist/lib/execution/devcontainer.d.ts +2 -0
  116. package/dist/lib/execution/devcontainer.js +41 -9
  117. package/dist/lib/execution/runners.d.ts +85 -3
  118. package/dist/lib/execution/runners.js +925 -228
  119. package/dist/lib/execution/spawner.d.ts +2 -2
  120. package/dist/lib/execution/spawner.js +4 -3
  121. package/dist/lib/execution/storage.d.ts +2 -1
  122. package/dist/lib/execution/storage.js +9 -13
  123. package/dist/lib/execution/types.d.ts +10 -1
  124. package/dist/lib/execution/types.js +3 -1
  125. package/dist/lib/init/index.js +1 -0
  126. package/dist/lib/machine-config.js +1 -1
  127. package/dist/lib/pmo/base-command.js +5 -9
  128. package/dist/lib/pmo/index.js +2 -0
  129. package/dist/lib/pmo/schema.d.ts +6 -0
  130. package/dist/lib/pmo/schema.js +36 -0
  131. package/dist/lib/pmo/storage/base.js +3 -3
  132. package/dist/lib/pmo/storage/index.d.ts +16 -1
  133. package/dist/lib/pmo/storage/index.js +45 -0
  134. package/dist/lib/pmo/storage/roadmaps.d.ts +62 -0
  135. package/dist/lib/pmo/storage/roadmaps.js +301 -0
  136. package/dist/lib/pmo/storage/specs.js +2 -0
  137. package/dist/lib/pmo/storage/types.d.ts +14 -0
  138. package/dist/lib/pmo/sync-manager.d.ts +1 -1
  139. package/dist/lib/pmo/sync-manager.js +1 -1
  140. package/dist/lib/pmo/types.d.ts +41 -0
  141. package/dist/lib/pmo/utils.d.ts +2 -0
  142. package/dist/lib/pmo/utils.js +22 -1
  143. package/dist/lib/repos/index.js +7 -1
  144. package/dist/lib/terminal.d.ts +31 -0
  145. package/dist/lib/terminal.js +48 -0
  146. package/dist/lib/themes.d.ts +21 -3
  147. package/dist/lib/themes.js +80 -23
  148. package/dist/lib/workspace-config.d.ts +80 -0
  149. package/dist/lib/workspace-config.js +100 -0
  150. package/oclif.manifest.json +4065 -3225
  151. package/package.json +10 -6
  152. 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, } from '../../lib/prompt-json.js';
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
- for (const dep of dependsOn) {
165
- const depSpec = await this.storage.getSpec(dep.dependsOnSpecId);
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
- for (const dep of otherDeps) {
174
- const relatedSpec = await this.storage.getSpec(dep.dependsOnSpecId);
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
- const dependedBy = [];
182
- for (const otherSpec of allSpecs) {
183
- if (otherSpec.id === specId)
184
- continue;
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
- if (dep)
188
- dependedBy.push({ spec: otherSpec, type: dep.dependencyType });
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 { spec: depSpec, type } of dependedBy)
193
- this.log(` - ${depSpec.id}: ${depSpec.title} (${type})`);
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, } from '../../lib/prompt-json.js';
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(l => l)
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(l => l)
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
- updates.priority = flags.priority === 'none' ? undefined : flags.priority;
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;