@proletariat/cli 0.3.23 → 0.3.24
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/action/create.js +1 -1
- package/dist/commands/agent/{temp/cleanup.d.ts → cleanup.d.ts} +1 -1
- package/dist/commands/agent/{temp/cleanup.js → cleanup.js} +4 -4
- package/dist/commands/agent/index.js +8 -8
- package/dist/commands/branch/create.js +2 -2
- package/dist/commands/epic/create.d.ts +1 -0
- package/dist/commands/epic/create.js +39 -2
- package/dist/commands/epic/index.js +2 -2
- package/dist/commands/{epic/link/remove.d.ts → link/create.d.ts} +6 -7
- package/dist/commands/link/create.js +141 -0
- package/dist/commands/{epic/link/relates.d.ts → link/index.d.ts} +4 -5
- package/dist/commands/link/index.js +87 -0
- package/dist/commands/{epic/link/duplicates.d.ts → link/list.d.ts} +7 -4
- package/dist/commands/link/list.js +182 -0
- package/dist/commands/{spec/link → link}/remove.d.ts +4 -5
- package/dist/commands/link/remove.js +120 -0
- package/dist/commands/mcp-server.d.ts +22 -0
- package/dist/commands/mcp-server.js +98 -0
- package/dist/commands/phase/create.js +1 -1
- package/dist/commands/project/create.d.ts +1 -0
- package/dist/commands/project/create.js +38 -4
- package/dist/commands/spec/create.d.ts +1 -0
- package/dist/commands/spec/create.js +43 -2
- package/dist/commands/spec/index.js +2 -2
- package/dist/commands/{agent/staff → staff}/add.js +10 -10
- package/dist/commands/{agent/staff → staff}/index.d.ts +1 -1
- package/dist/commands/{agent/staff → staff}/index.js +7 -7
- package/dist/commands/{agent/staff → staff}/list.js +3 -3
- package/dist/commands/{agent/staff → staff}/remove.d.ts +1 -1
- package/dist/commands/{agent/staff → staff}/remove.js +8 -8
- package/dist/commands/{ticket/template → template}/apply.d.ts +8 -6
- package/dist/commands/template/apply.js +262 -0
- package/dist/commands/{ticket/template → template}/create.d.ts +5 -6
- package/dist/commands/template/create.js +238 -0
- package/dist/commands/template/index.js +48 -36
- package/dist/commands/{ticket/template → template}/save.d.ts +2 -2
- package/dist/commands/template/save.js +104 -0
- package/dist/commands/{phase/template → template}/update.d.ts +2 -2
- package/dist/commands/template/update.js +99 -0
- package/dist/commands/{agent/themes → theme}/add-names.d.ts +1 -1
- package/dist/commands/{agent/themes → theme}/add-names.js +6 -6
- package/dist/commands/{agent/themes → theme}/create.d.ts +1 -1
- package/dist/commands/{agent/themes → theme}/create.js +5 -5
- package/dist/commands/{agent/themes → theme}/index.d.ts +1 -1
- package/dist/commands/{agent/themes → theme}/index.js +10 -10
- package/dist/commands/{agent/themes → theme}/list.d.ts +1 -1
- package/dist/commands/{agent/themes → theme}/list.js +5 -5
- package/dist/commands/{agent/themes → theme}/set.d.ts +1 -1
- package/dist/commands/{agent/themes → theme}/set.js +7 -7
- package/dist/commands/ticket/create.d.ts +1 -0
- package/dist/commands/ticket/create.js +54 -2
- package/dist/commands/ticket/index.js +6 -6
- package/dist/commands/work/spawn.js +1 -1
- package/dist/lib/mcp/helpers.d.ts +43 -0
- package/dist/lib/mcp/helpers.js +57 -0
- package/dist/lib/mcp/index.d.ts +6 -0
- package/dist/lib/mcp/index.js +6 -0
- package/dist/lib/mcp/tools/action.d.ts +6 -0
- package/dist/lib/mcp/tools/action.js +88 -0
- package/dist/lib/mcp/tools/board.d.ts +6 -0
- package/dist/lib/mcp/tools/board.js +139 -0
- package/dist/lib/mcp/tools/category.d.ts +6 -0
- package/dist/lib/mcp/tools/category.js +84 -0
- package/dist/lib/mcp/tools/cli-passthrough.d.ts +15 -0
- package/dist/lib/mcp/tools/cli-passthrough.js +333 -0
- package/dist/lib/mcp/tools/epic.d.ts +6 -0
- package/dist/lib/mcp/tools/epic.js +178 -0
- package/dist/lib/mcp/tools/index.d.ts +18 -0
- package/dist/lib/mcp/tools/index.js +19 -0
- package/dist/lib/mcp/tools/phase.d.ts +6 -0
- package/dist/lib/mcp/tools/phase.js +131 -0
- package/dist/lib/mcp/tools/project.d.ts +6 -0
- package/dist/lib/mcp/tools/project.js +196 -0
- package/dist/lib/mcp/tools/roadmap.d.ts +6 -0
- package/dist/lib/mcp/tools/roadmap.js +123 -0
- package/dist/lib/mcp/tools/spec.d.ts +6 -0
- package/dist/lib/mcp/tools/spec.js +196 -0
- package/dist/lib/mcp/tools/status.d.ts +6 -0
- package/dist/lib/mcp/tools/status.js +109 -0
- package/dist/lib/mcp/tools/template.d.ts +6 -0
- package/dist/lib/mcp/tools/template.js +107 -0
- package/dist/lib/mcp/tools/ticket.d.ts +6 -0
- package/dist/lib/mcp/tools/ticket.js +393 -0
- package/dist/lib/mcp/tools/view.d.ts +6 -0
- package/dist/lib/mcp/tools/view.js +76 -0
- package/dist/lib/mcp/tools/work.d.ts +6 -0
- package/dist/lib/mcp/tools/work.js +132 -0
- package/dist/lib/mcp/tools/workflow.d.ts +6 -0
- package/dist/lib/mcp/tools/workflow.js +95 -0
- package/dist/lib/mcp/types.d.ts +17 -0
- package/dist/lib/mcp/types.js +4 -0
- package/dist/lib/prompt-json.d.ts +52 -1
- package/dist/lib/prompt-json.js +45 -0
- package/oclif.manifest.json +3660 -5564
- package/package.json +6 -4
- package/dist/commands/agent/temp/index.d.ts +0 -14
- package/dist/commands/agent/temp/index.js +0 -85
- package/dist/commands/agent/temp/list.d.ts +0 -7
- package/dist/commands/agent/temp/list.js +0 -108
- package/dist/commands/epic/link/block.d.ts +0 -14
- package/dist/commands/epic/link/block.js +0 -81
- package/dist/commands/epic/link/duplicates.js +0 -68
- package/dist/commands/epic/link/index.d.ts +0 -19
- package/dist/commands/epic/link/index.js +0 -272
- package/dist/commands/epic/link/relates.js +0 -68
- package/dist/commands/epic/link/remove.js +0 -93
- package/dist/commands/phase/template/apply.d.ts +0 -17
- package/dist/commands/phase/template/apply.js +0 -108
- package/dist/commands/phase/template/create.d.ts +0 -17
- package/dist/commands/phase/template/create.js +0 -104
- package/dist/commands/phase/template/delete.d.ts +0 -17
- package/dist/commands/phase/template/delete.js +0 -100
- package/dist/commands/phase/template/index.d.ts +0 -15
- package/dist/commands/phase/template/index.js +0 -130
- package/dist/commands/phase/template/list.d.ts +0 -16
- package/dist/commands/phase/template/list.js +0 -97
- package/dist/commands/phase/template/update.js +0 -89
- package/dist/commands/spec/link/depends.d.ts +0 -14
- package/dist/commands/spec/link/depends.js +0 -64
- package/dist/commands/spec/link/duplicates.d.ts +0 -14
- package/dist/commands/spec/link/duplicates.js +0 -63
- package/dist/commands/spec/link/index.d.ts +0 -19
- package/dist/commands/spec/link/index.js +0 -207
- package/dist/commands/spec/link/relates.d.ts +0 -14
- package/dist/commands/spec/link/relates.js +0 -63
- package/dist/commands/spec/link/remove.js +0 -96
- package/dist/commands/template/phase/apply.d.ts +0 -14
- package/dist/commands/template/phase/apply.js +0 -43
- package/dist/commands/template/phase/create.d.ts +0 -13
- package/dist/commands/template/phase/create.js +0 -38
- package/dist/commands/template/phase/delete.d.ts +0 -13
- package/dist/commands/template/phase/delete.js +0 -36
- package/dist/commands/template/phase/index.d.ts +0 -10
- package/dist/commands/template/phase/index.js +0 -63
- package/dist/commands/template/phase/list.d.ts +0 -11
- package/dist/commands/template/phase/list.js +0 -36
- package/dist/commands/template/phase/update.d.ts +0 -14
- package/dist/commands/template/phase/update.js +0 -43
- package/dist/commands/template/ticket/apply.d.ts +0 -17
- package/dist/commands/template/ticket/apply.js +0 -60
- package/dist/commands/template/ticket/create.d.ts +0 -20
- package/dist/commands/template/ticket/create.js +0 -89
- package/dist/commands/template/ticket/delete.d.ts +0 -13
- package/dist/commands/template/ticket/delete.js +0 -38
- package/dist/commands/template/ticket/index.d.ts +0 -10
- package/dist/commands/template/ticket/index.js +0 -63
- package/dist/commands/template/ticket/list.d.ts +0 -11
- package/dist/commands/template/ticket/list.js +0 -36
- package/dist/commands/template/ticket/save.d.ts +0 -15
- package/dist/commands/template/ticket/save.js +0 -46
- package/dist/commands/ticket/link/block.d.ts +0 -14
- package/dist/commands/ticket/link/block.js +0 -96
- package/dist/commands/ticket/link/duplicates.d.ts +0 -14
- package/dist/commands/ticket/link/duplicates.js +0 -95
- package/dist/commands/ticket/link/index.d.ts +0 -19
- package/dist/commands/ticket/link/index.js +0 -256
- package/dist/commands/ticket/link/relates.d.ts +0 -14
- package/dist/commands/ticket/link/relates.js +0 -95
- package/dist/commands/ticket/link/remove.d.ts +0 -16
- package/dist/commands/ticket/link/remove.js +0 -132
- package/dist/commands/ticket/template/apply.js +0 -252
- package/dist/commands/ticket/template/create.js +0 -386
- package/dist/commands/ticket/template/delete.d.ts +0 -17
- package/dist/commands/ticket/template/delete.js +0 -94
- package/dist/commands/ticket/template/index.d.ts +0 -15
- package/dist/commands/ticket/template/index.js +0 -120
- package/dist/commands/ticket/template/list.d.ts +0 -16
- package/dist/commands/ticket/template/list.js +0 -112
- package/dist/commands/ticket/template/save.js +0 -163
- /package/dist/commands/{agent/staff → staff}/add.d.ts +0 -0
- /package/dist/commands/{agent/staff → staff}/list.d.ts +0 -0
|
@@ -21,7 +21,7 @@ export default class ActionCreate extends PMOCommand {
|
|
|
21
21
|
...pmoBaseFlags,
|
|
22
22
|
prompt: Flags.string({
|
|
23
23
|
char: 'p',
|
|
24
|
-
description: 'The prompt to send to the agent',
|
|
24
|
+
description: 'The prompt to send to the agent [required for non-interactive]',
|
|
25
25
|
}),
|
|
26
26
|
description: Flags.string({
|
|
27
27
|
char: 'd',
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import inquirer from 'inquirer';
|
|
3
|
-
import { colors, format } from '
|
|
4
|
-
import { getWorkspaceInfo, cleanupAgent, getCleanableAgents, getAgentTmuxSessions } from '
|
|
5
|
-
import { PMOCommand, pmoBaseFlags } from '
|
|
6
|
-
import { shouldOutputJson, outputPromptAsJson, outputErrorAsJson, outputSuccessAsJson, createMetadata, buildPromptConfig, } from '
|
|
3
|
+
import { colors, format } from '../../lib/colors.js';
|
|
4
|
+
import { getWorkspaceInfo, cleanupAgent, getCleanableAgents, getAgentTmuxSessions } from '../../lib/agents/commands.js';
|
|
5
|
+
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
6
|
+
import { shouldOutputJson, outputPromptAsJson, outputErrorAsJson, outputSuccessAsJson, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
|
|
7
7
|
export default class Cleanup extends PMOCommand {
|
|
8
8
|
static description = 'Clean up agent resources (containers, directories, tmux sessions)';
|
|
9
9
|
static examples = [
|
|
@@ -10,7 +10,7 @@ export default class Agent extends PMOCommand {
|
|
|
10
10
|
'<%= config.bin %> <%= command.id %> visit tacoma',
|
|
11
11
|
'<%= config.bin %> <%= command.id %> staff add',
|
|
12
12
|
'<%= config.bin %> <%= command.id %> staff remove camry',
|
|
13
|
-
'<%= config.bin %> <%= command.id %>
|
|
13
|
+
'<%= config.bin %> <%= command.id %> cleanup --temp',
|
|
14
14
|
'<%= config.bin %> <%= command.id %> restart altman',
|
|
15
15
|
'<%= config.bin %> <%= command.id %> rebuild altman',
|
|
16
16
|
'<%= config.bin %> <%= command.id %> shell altman',
|
|
@@ -52,7 +52,7 @@ export default class Agent extends PMOCommand {
|
|
|
52
52
|
{ name: '🗑️ Remove agent', value: 'remove', command: 'prlt agent remove --machine' },
|
|
53
53
|
// Management group
|
|
54
54
|
{ name: '👔 Manage staff agents', value: 'staff', command: 'prlt agent staff --machine' },
|
|
55
|
-
{ name: '
|
|
55
|
+
{ name: '🧹 Cleanup agents', value: 'cleanup', command: 'prlt agent cleanup --machine' },
|
|
56
56
|
{ name: '🎨 Manage themes', value: 'themes', command: 'prlt agent themes --machine' },
|
|
57
57
|
// Operations group
|
|
58
58
|
{ name: '🐚 Open shell', value: 'shell', command: 'prlt agent shell --machine' },
|
|
@@ -96,20 +96,20 @@ export default class Agent extends PMOCommand {
|
|
|
96
96
|
break;
|
|
97
97
|
}
|
|
98
98
|
case 'staff': {
|
|
99
|
-
const { default: StaffCommand } = await import('
|
|
99
|
+
const { default: StaffCommand } = await import('../staff/index.js');
|
|
100
100
|
const cmd = new StaffCommand([], this.config);
|
|
101
101
|
await cmd.run();
|
|
102
102
|
break;
|
|
103
103
|
}
|
|
104
|
-
case '
|
|
105
|
-
const { default:
|
|
106
|
-
const cmd = new
|
|
104
|
+
case 'cleanup': {
|
|
105
|
+
const { default: CleanupCommand } = await import('./cleanup.js');
|
|
106
|
+
const cmd = new CleanupCommand([], this.config);
|
|
107
107
|
await cmd.run();
|
|
108
108
|
break;
|
|
109
109
|
}
|
|
110
110
|
case 'themes': {
|
|
111
|
-
const { default:
|
|
112
|
-
const cmd = new
|
|
111
|
+
const { default: ThemeCommand } = await import('../theme/index.js');
|
|
112
|
+
const cmd = new ThemeCommand([], this.config);
|
|
113
113
|
await cmd.run();
|
|
114
114
|
break;
|
|
115
115
|
}
|
|
@@ -38,7 +38,7 @@ export default class BranchCreate extends PMOCommand {
|
|
|
38
38
|
}),
|
|
39
39
|
type: Flags.string({
|
|
40
40
|
char: 't',
|
|
41
|
-
description: 'Branch type',
|
|
41
|
+
description: 'Branch type [required for non-interactive with -d]',
|
|
42
42
|
options: Object.keys(BRANCH_TYPES),
|
|
43
43
|
}),
|
|
44
44
|
owner: Flags.string({
|
|
@@ -47,7 +47,7 @@ export default class BranchCreate extends PMOCommand {
|
|
|
47
47
|
}),
|
|
48
48
|
description: Flags.string({
|
|
49
49
|
char: 'd',
|
|
50
|
-
description: 'Branch description (kebab-case)',
|
|
50
|
+
description: 'Branch description (kebab-case) [required for non-interactive with -t]',
|
|
51
51
|
}),
|
|
52
52
|
'empty-commit': Flags.boolean({
|
|
53
53
|
char: 'e',
|
|
@@ -8,6 +8,7 @@ export default class EpicCreate extends PMOCommand {
|
|
|
8
8
|
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
9
|
spec: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
10
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
12
|
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
13
|
};
|
|
13
14
|
execute(): Promise<void>;
|
|
@@ -3,7 +3,7 @@ import inquirer from 'inquirer';
|
|
|
3
3
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
4
4
|
import { styles } from '../../lib/styles.js';
|
|
5
5
|
import { createEpicFile, getRelativeEpicPath } from '../../lib/pmo/epic-files.js';
|
|
6
|
-
import { shouldOutputJson, outputPromptAsJson, createMetadata, buildFormPromptConfig, } from '../../lib/prompt-json.js';
|
|
6
|
+
import { shouldOutputJson, outputPromptAsJson, outputDryRunSuccessAsJson, outputDryRunErrorsAsJson, createMetadata, buildFormPromptConfig, } from '../../lib/prompt-json.js';
|
|
7
7
|
export default class EpicCreate extends PMOCommand {
|
|
8
8
|
static description = 'Create a new epic';
|
|
9
9
|
static examples = [
|
|
@@ -11,12 +11,13 @@ export default class EpicCreate extends PMOCommand {
|
|
|
11
11
|
'<%= config.bin %> <%= command.id %> --title "User Authentication System"',
|
|
12
12
|
'<%= config.bin %> <%= command.id %> -t "API Design" --status draft',
|
|
13
13
|
'<%= config.bin %> <%= command.id %> -t "Implement Auth" --spec SPEC-001',
|
|
14
|
+
'<%= config.bin %> <%= command.id %> --title "Test" -P PROJ-001 --dry-run --json # Validate without creating',
|
|
14
15
|
];
|
|
15
16
|
static flags = {
|
|
16
17
|
...pmoBaseFlags,
|
|
17
18
|
title: Flags.string({
|
|
18
19
|
char: 't',
|
|
19
|
-
description: 'Epic title',
|
|
20
|
+
description: 'Epic title [required for non-interactive]',
|
|
20
21
|
}),
|
|
21
22
|
status: Flags.string({
|
|
22
23
|
char: 's',
|
|
@@ -37,6 +38,10 @@ export default class EpicCreate extends PMOCommand {
|
|
|
37
38
|
description: 'Output prompt configuration as JSON (for AI agents/scripts)',
|
|
38
39
|
default: false,
|
|
39
40
|
}),
|
|
41
|
+
'dry-run': Flags.boolean({
|
|
42
|
+
description: 'Validate inputs without creating epic (use with --json for structured output)',
|
|
43
|
+
default: false,
|
|
44
|
+
}),
|
|
40
45
|
};
|
|
41
46
|
async execute() {
|
|
42
47
|
const { flags } = await this.parse(EpicCreate);
|
|
@@ -90,9 +95,41 @@ export default class EpicCreate extends PMOCommand {
|
|
|
90
95
|
if (epicData.specId) {
|
|
91
96
|
const spec = await this.storage.getSpec(epicData.specId);
|
|
92
97
|
if (!spec) {
|
|
98
|
+
if (flags['dry-run']) {
|
|
99
|
+
if (jsonMode) {
|
|
100
|
+
outputDryRunErrorsAsJson([{ field: 'spec', error: `Spec not found: ${epicData.specId}` }], createMetadata('epic create', flags));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
93
103
|
this.error(`Spec not found: ${epicData.specId}`);
|
|
94
104
|
}
|
|
95
105
|
}
|
|
106
|
+
// Handle dry-run: show what would be created without actually creating
|
|
107
|
+
if (flags['dry-run']) {
|
|
108
|
+
const projectName = await this.getProjectName(projectId);
|
|
109
|
+
const wouldCreate = {
|
|
110
|
+
title: epicData.title,
|
|
111
|
+
project: projectId,
|
|
112
|
+
status: epicData.status,
|
|
113
|
+
...(epicData.description && { description: epicData.description }),
|
|
114
|
+
...(epicData.specId && { spec: epicData.specId }),
|
|
115
|
+
};
|
|
116
|
+
if (jsonMode) {
|
|
117
|
+
outputDryRunSuccessAsJson('epic', wouldCreate, createMetadata('epic create', flags));
|
|
118
|
+
}
|
|
119
|
+
// Human-readable dry-run output
|
|
120
|
+
this.log(styles.warning('\n[DRY RUN] Would create epic:'));
|
|
121
|
+
this.log(styles.muted(` Title: ${epicData.title}`));
|
|
122
|
+
this.log(styles.muted(` Project: ${projectName}`));
|
|
123
|
+
this.log(styles.muted(` Status: ${epicData.status}`));
|
|
124
|
+
if (epicData.description) {
|
|
125
|
+
this.log(styles.muted(` Description: ${epicData.description}`));
|
|
126
|
+
}
|
|
127
|
+
if (epicData.specId) {
|
|
128
|
+
this.log(styles.muted(` Spec: ${epicData.specId}`));
|
|
129
|
+
}
|
|
130
|
+
this.log(styles.muted('\n(No epic was created)'));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
96
133
|
const epic = await this.storage.createEpic(projectId, {
|
|
97
134
|
title: epicData.title,
|
|
98
135
|
status: epicData.status,
|
|
@@ -31,7 +31,7 @@ export default class Epic extends PMOCommand {
|
|
|
31
31
|
{ id: 'progress', name: 'Show progress', command: 'prlt epic progress --json' },
|
|
32
32
|
{ id: 'ticket', name: 'Assign tickets to epic', command: 'prlt epic ticket --json' },
|
|
33
33
|
{ id: 'spec', name: 'Assign spec to epic', command: 'prlt epic spec --json' },
|
|
34
|
-
{ id: 'link', name: 'Manage dependencies', command: 'prlt
|
|
34
|
+
{ id: 'link', name: 'Manage dependencies', command: 'prlt link list --json' },
|
|
35
35
|
{ id: 'archive', name: 'Archive epic (complete)', command: 'prlt epic archive --json' },
|
|
36
36
|
{ id: 'activate', name: 'Activate epic', command: 'prlt epic activate --json' },
|
|
37
37
|
{ id: 'move', name: 'Reorder epic', command: 'prlt epic move --json' },
|
|
@@ -72,7 +72,7 @@ export default class Epic extends PMOCommand {
|
|
|
72
72
|
await this.config.runCommand('epic:spec', []);
|
|
73
73
|
break;
|
|
74
74
|
case 'link':
|
|
75
|
-
await this.config.runCommand('
|
|
75
|
+
await this.config.runCommand('link', []);
|
|
76
76
|
break;
|
|
77
77
|
case 'archive':
|
|
78
78
|
await this.config.runCommand('epic:archive', []);
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import { PMOCommand } from '
|
|
2
|
-
export default class
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
export default class LinkCreate extends PMOCommand {
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static args: {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
from: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
to: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
8
8
|
};
|
|
9
9
|
static flags: {
|
|
10
|
-
|
|
11
|
-
type: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
-
all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
type: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
11
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
13
|
};
|
|
15
14
|
execute(): Promise<void>;
|
|
16
15
|
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { autoExportToBoard, PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
3
|
+
import { styles } from '../../lib/styles.js';
|
|
4
|
+
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
5
|
+
/**
|
|
6
|
+
* Infer entity type from ID prefix
|
|
7
|
+
*/
|
|
8
|
+
function inferEntityType(id) {
|
|
9
|
+
const upper = id.toUpperCase();
|
|
10
|
+
if (upper.startsWith('TKT-'))
|
|
11
|
+
return 'ticket';
|
|
12
|
+
if (upper.startsWith('SPEC-'))
|
|
13
|
+
return 'spec';
|
|
14
|
+
if (upper.startsWith('EPIC-'))
|
|
15
|
+
return 'epic';
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
export default class LinkCreate extends PMOCommand {
|
|
19
|
+
static description = 'Create a link (dependency) between two entities';
|
|
20
|
+
static examples = [
|
|
21
|
+
'<%= config.bin %> <%= command.id %> TKT-001 TKT-002 --type blocks # TKT-001 is blocked by TKT-002',
|
|
22
|
+
'<%= config.bin %> <%= command.id %> TKT-001 TKT-002 --type relates # TKT-001 relates to TKT-002',
|
|
23
|
+
'<%= config.bin %> <%= command.id %> SPEC-001 SPEC-002 --type depends # SPEC-001 depends on SPEC-002',
|
|
24
|
+
'<%= config.bin %> <%= command.id %> EPIC-001 EPIC-002 --type blocks # EPIC-001 is blocked by EPIC-002',
|
|
25
|
+
];
|
|
26
|
+
static args = {
|
|
27
|
+
from: Args.string({
|
|
28
|
+
description: 'Source entity ID (TKT-xxx, SPEC-xxx, or EPIC-xxx)',
|
|
29
|
+
required: true,
|
|
30
|
+
}),
|
|
31
|
+
to: Args.string({
|
|
32
|
+
description: 'Target entity ID',
|
|
33
|
+
required: true,
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
static flags = {
|
|
37
|
+
...pmoBaseFlags,
|
|
38
|
+
type: Flags.string({
|
|
39
|
+
char: 't',
|
|
40
|
+
description: 'Link type',
|
|
41
|
+
options: ['blocks', 'relates', 'duplicates', 'depends'],
|
|
42
|
+
required: true,
|
|
43
|
+
}),
|
|
44
|
+
json: Flags.boolean({
|
|
45
|
+
char: 'm',
|
|
46
|
+
aliases: ['machine'],
|
|
47
|
+
description: 'Output prompt configuration as JSON (for AI agents/scripts)',
|
|
48
|
+
default: false,
|
|
49
|
+
}),
|
|
50
|
+
};
|
|
51
|
+
async execute() {
|
|
52
|
+
const { args, flags } = await this.parse(LinkCreate);
|
|
53
|
+
const jsonMode = shouldOutputJson(flags);
|
|
54
|
+
const handleError = (code, message) => {
|
|
55
|
+
if (jsonMode) {
|
|
56
|
+
outputErrorAsJson(code, message, createMetadata('link create', flags));
|
|
57
|
+
this.exit(1);
|
|
58
|
+
}
|
|
59
|
+
this.error(message);
|
|
60
|
+
};
|
|
61
|
+
const fromType = inferEntityType(args.from);
|
|
62
|
+
const toType = inferEntityType(args.to);
|
|
63
|
+
if (!fromType) {
|
|
64
|
+
return handleError('INVALID_FROM_ID', `Cannot infer entity type from "${args.from}". Use TKT-, SPEC-, or EPIC- prefix.`);
|
|
65
|
+
}
|
|
66
|
+
if (!toType) {
|
|
67
|
+
return handleError('INVALID_TO_ID', `Cannot infer entity type from "${args.to}". Use TKT-, SPEC-, or EPIC- prefix.`);
|
|
68
|
+
}
|
|
69
|
+
if (fromType !== toType) {
|
|
70
|
+
return handleError('TYPE_MISMATCH', `Cannot link different entity types: ${fromType} and ${toType}`);
|
|
71
|
+
}
|
|
72
|
+
const linkType = flags.type;
|
|
73
|
+
// Validate link type for entity type
|
|
74
|
+
// Tickets: blocks, relates_to, duplicates
|
|
75
|
+
// Specs: depends_on, relates_to, duplicates (no blocks)
|
|
76
|
+
// Epics: blocks, relates_to, duplicates (no depends)
|
|
77
|
+
if (fromType === 'spec' && linkType === 'blocks') {
|
|
78
|
+
return handleError('INVALID_LINK_TYPE', 'Specs do not support "blocks" link type. Use "depends" instead.');
|
|
79
|
+
}
|
|
80
|
+
if (fromType === 'epic' && linkType === 'depends') {
|
|
81
|
+
return handleError('INVALID_LINK_TYPE', 'Epics do not support "depends" link type. Use "blocks" instead.');
|
|
82
|
+
}
|
|
83
|
+
if (fromType === 'ticket' && linkType === 'depends') {
|
|
84
|
+
return handleError('INVALID_LINK_TYPE', 'Tickets do not support "depends" link type. Use "blocks" instead.');
|
|
85
|
+
}
|
|
86
|
+
// Map the link type to the storage dependency type
|
|
87
|
+
const dependencyType = linkType === 'relates' ? 'relates_to' :
|
|
88
|
+
linkType === 'depends' ? 'depends_on' : linkType;
|
|
89
|
+
try {
|
|
90
|
+
if (fromType === 'ticket') {
|
|
91
|
+
const ticket = await this.storage.getTicket(args.from);
|
|
92
|
+
if (!ticket)
|
|
93
|
+
return handleError('FROM_NOT_FOUND', `Ticket not found: ${args.from}`);
|
|
94
|
+
const targetTicket = await this.storage.getTicket(args.to);
|
|
95
|
+
if (!targetTicket)
|
|
96
|
+
return handleError('TO_NOT_FOUND', `Ticket not found: ${args.to}`);
|
|
97
|
+
await this.storage.createTicketDependency(args.from, args.to, dependencyType);
|
|
98
|
+
await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
|
|
99
|
+
this.log(styles.success(`\n✅ Link created: ${styles.emphasis(args.from)} → ${styles.emphasis(args.to)} (${linkType})`));
|
|
100
|
+
this.log(styles.muted(` ${ticket.title}`));
|
|
101
|
+
this.log(styles.muted(` ${linkType}: ${targetTicket.title}`));
|
|
102
|
+
}
|
|
103
|
+
else if (fromType === 'spec') {
|
|
104
|
+
const spec = await this.storage.getSpec(args.from);
|
|
105
|
+
if (!spec)
|
|
106
|
+
return handleError('FROM_NOT_FOUND', `Spec not found: ${args.from}`);
|
|
107
|
+
const targetSpec = await this.storage.getSpec(args.to);
|
|
108
|
+
if (!targetSpec)
|
|
109
|
+
return handleError('TO_NOT_FOUND', `Spec not found: ${args.to}`);
|
|
110
|
+
await this.storage.createSpecDependency(args.from, args.to, dependencyType);
|
|
111
|
+
this.log(styles.success(`\n✅ Link created: ${styles.emphasis(args.from)} → ${styles.emphasis(args.to)} (${linkType})`));
|
|
112
|
+
this.log(styles.muted(` ${spec.title}`));
|
|
113
|
+
this.log(styles.muted(` ${linkType}: ${targetSpec.title}`));
|
|
114
|
+
}
|
|
115
|
+
else if (fromType === 'epic') {
|
|
116
|
+
const epic = await this.storage.getEpic(args.from);
|
|
117
|
+
if (!epic)
|
|
118
|
+
return handleError('FROM_NOT_FOUND', `Epic not found: ${args.from}`);
|
|
119
|
+
const targetEpic = await this.storage.getEpic(args.to);
|
|
120
|
+
if (!targetEpic)
|
|
121
|
+
return handleError('TO_NOT_FOUND', `Epic not found: ${args.to}`);
|
|
122
|
+
await this.storage.createEpicDependency(args.from, args.to, dependencyType);
|
|
123
|
+
await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
|
|
124
|
+
this.log(styles.success(`\n✅ Link created: ${styles.emphasis(args.from)} → ${styles.emphasis(args.to)} (${linkType})`));
|
|
125
|
+
this.log(styles.muted(` ${epic.title}`));
|
|
126
|
+
this.log(styles.muted(` ${linkType}: ${targetEpic.title}`));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
if (error instanceof Error) {
|
|
131
|
+
if (error.message.includes('already exists')) {
|
|
132
|
+
return handleError('ALREADY_EXISTS', 'Link already exists');
|
|
133
|
+
}
|
|
134
|
+
if (error.message.includes('self-dependency')) {
|
|
135
|
+
return handleError('SELF_LINK', 'Cannot create self-link');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { PMOCommand } from '
|
|
2
|
-
export default class
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
export default class Link extends PMOCommand {
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static args: {
|
|
6
|
-
|
|
7
|
-
target: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
6
|
+
action: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
8
7
|
};
|
|
9
8
|
static flags: {
|
|
10
|
-
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
9
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
11
|
};
|
|
13
12
|
execute(): Promise<void>;
|
|
14
13
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
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, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
|
|
6
|
+
export default class Link extends PMOCommand {
|
|
7
|
+
static description = 'Manage links (dependencies) between entities';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> <%= command.id %> create TKT-001 TKT-002 --type blocks',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> list TKT-001',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> remove TKT-001 TKT-002',
|
|
12
|
+
];
|
|
13
|
+
static args = {
|
|
14
|
+
action: Args.string({
|
|
15
|
+
description: 'Action to perform',
|
|
16
|
+
options: ['create', 'list', 'remove'],
|
|
17
|
+
required: false,
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
static flags = {
|
|
21
|
+
...pmoBaseFlags,
|
|
22
|
+
json: Flags.boolean({
|
|
23
|
+
char: 'm',
|
|
24
|
+
aliases: ['machine'],
|
|
25
|
+
description: 'Output prompt configuration as JSON (for AI agents/scripts)',
|
|
26
|
+
default: false,
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
async execute() {
|
|
30
|
+
const { args, flags } = await this.parse(Link);
|
|
31
|
+
const jsonMode = shouldOutputJson(flags);
|
|
32
|
+
let action = args.action;
|
|
33
|
+
if (!action) {
|
|
34
|
+
const menuChoices = [
|
|
35
|
+
{ name: 'Create a new link', value: 'create', command: 'prlt link create --help' },
|
|
36
|
+
{ name: 'List links for an entity', value: 'list', command: 'prlt link list --help' },
|
|
37
|
+
{ name: 'Remove a link', value: 'remove', command: 'prlt link remove --help' },
|
|
38
|
+
{ name: 'Cancel', value: 'cancel', command: '' },
|
|
39
|
+
];
|
|
40
|
+
const message = 'What would you like to do?';
|
|
41
|
+
if (jsonMode) {
|
|
42
|
+
outputPromptAsJson(buildPromptConfig('list', 'action', message, menuChoices), createMetadata('link', flags));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const { selected } = await inquirer.prompt([{
|
|
46
|
+
type: 'list',
|
|
47
|
+
name: 'selected',
|
|
48
|
+
message,
|
|
49
|
+
choices: [
|
|
50
|
+
...menuChoices.slice(0, 3).map(c => ({ name: c.name, value: c.value })),
|
|
51
|
+
new inquirer.Separator(),
|
|
52
|
+
{ name: menuChoices[3].name, value: menuChoices[3].value }
|
|
53
|
+
]
|
|
54
|
+
}]);
|
|
55
|
+
if (selected === 'cancel') {
|
|
56
|
+
this.log(styles.muted('\nCancelled.'));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
action = selected;
|
|
60
|
+
}
|
|
61
|
+
// Show help for the selected action
|
|
62
|
+
this.log(styles.muted(`\nUsage:`));
|
|
63
|
+
switch (action) {
|
|
64
|
+
case 'create':
|
|
65
|
+
this.log(' prlt link create <from> <to> --type <blocks|relates|duplicates|depends>');
|
|
66
|
+
this.log('');
|
|
67
|
+
this.log('Examples:');
|
|
68
|
+
this.log(' prlt link create TKT-001 TKT-002 --type blocks # TKT-001 is blocked by TKT-002');
|
|
69
|
+
this.log(' prlt link create SPEC-001 SPEC-002 --type depends # SPEC-001 depends on SPEC-002');
|
|
70
|
+
break;
|
|
71
|
+
case 'list':
|
|
72
|
+
this.log(' prlt link list <entity-id>');
|
|
73
|
+
this.log('');
|
|
74
|
+
this.log('Examples:');
|
|
75
|
+
this.log(' prlt link list TKT-001 # List links for ticket');
|
|
76
|
+
this.log(' prlt link list SPEC-001 --all # Include reverse links');
|
|
77
|
+
break;
|
|
78
|
+
case 'remove':
|
|
79
|
+
this.log(' prlt link remove <from> <to>');
|
|
80
|
+
this.log('');
|
|
81
|
+
this.log('Examples:');
|
|
82
|
+
this.log(' prlt link remove TKT-001 TKT-002');
|
|
83
|
+
this.log(' prlt link remove SPEC-001 SPEC-002 --type depends');
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import { PMOCommand } from '
|
|
2
|
-
export default class
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
export default class LinkList extends PMOCommand {
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static args: {
|
|
6
6
|
id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
-
original: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
8
7
|
};
|
|
9
8
|
static flags: {
|
|
10
|
-
|
|
9
|
+
all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
10
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
12
|
};
|
|
13
13
|
execute(): Promise<void>;
|
|
14
|
+
private listTicketLinks;
|
|
15
|
+
private listSpecLinks;
|
|
16
|
+
private listEpicLinks;
|
|
14
17
|
}
|