@proletariat/cli 0.3.23 → 0.3.25
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 +4 -4
- package/dist/commands/action/update.js +3 -3
- 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/activate.js +9 -17
- package/dist/commands/epic/archive.js +13 -24
- package/dist/commands/epic/create.d.ts +1 -0
- package/dist/commands/epic/create.js +46 -8
- package/dist/commands/epic/index.js +2 -2
- package/dist/commands/epic/move.js +28 -47
- package/dist/commands/epic/progress.js +10 -14
- package/dist/commands/epic/project.js +42 -59
- package/dist/commands/epic/reorder.js +25 -30
- package/dist/commands/epic/spec.d.ts +1 -0
- package/dist/commands/epic/spec.js +39 -40
- package/dist/commands/epic/ticket.d.ts +2 -0
- package/dist/commands/epic/ticket.js +63 -37
- package/dist/commands/feedback/index.d.ts +10 -0
- package/dist/commands/feedback/index.js +60 -0
- package/dist/commands/feedback/list.d.ts +12 -0
- package/dist/commands/feedback/list.js +126 -0
- package/dist/commands/feedback/submit.d.ts +16 -0
- package/dist/commands/feedback/submit.js +220 -0
- package/dist/commands/{template/phase/delete.d.ts → feedback/view.d.ts} +7 -5
- package/dist/commands/feedback/view.js +109 -0
- package/dist/commands/gh/index.js +4 -0
- 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/repo/create.d.ts +38 -0
- package/dist/commands/repo/create.js +283 -0
- package/dist/commands/repo/index.js +7 -0
- package/dist/commands/roadmap/add-project.js +9 -22
- package/dist/commands/roadmap/create.d.ts +0 -1
- package/dist/commands/roadmap/create.js +46 -40
- package/dist/commands/roadmap/delete.js +10 -24
- package/dist/commands/roadmap/generate.d.ts +1 -0
- package/dist/commands/roadmap/generate.js +21 -22
- package/dist/commands/roadmap/remove-project.js +14 -34
- package/dist/commands/roadmap/reorder.js +19 -26
- package/dist/commands/roadmap/update.js +27 -26
- package/dist/commands/roadmap/view.js +5 -12
- package/dist/commands/session/attach.d.ts +1 -8
- package/dist/commands/session/attach.js +93 -59
- package/dist/commands/session/list.d.ts +0 -8
- package/dist/commands/session/list.js +130 -81
- package/dist/commands/spec/create.d.ts +1 -0
- package/dist/commands/spec/create.js +44 -3
- package/dist/commands/spec/edit.js +63 -33
- 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/{template/phase/index.d.ts → support/book.d.ts} +2 -2
- package/dist/commands/support/book.js +54 -0
- package/dist/commands/{template/ticket/index.d.ts → support/discord.d.ts} +2 -2
- package/dist/commands/support/discord.js +54 -0
- package/dist/commands/support/docs.d.ts +10 -0
- package/dist/commands/support/docs.js +54 -0
- package/dist/commands/support/index.d.ts +19 -0
- package/dist/commands/support/index.js +81 -0
- package/dist/commands/support/issues.d.ts +11 -0
- package/dist/commands/support/issues.js +77 -0
- package/dist/commands/support/logs.d.ts +18 -0
- package/dist/commands/support/logs.js +247 -0
- 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 +75 -15
- package/dist/commands/ticket/edit.js +44 -13
- package/dist/commands/ticket/index.js +6 -6
- package/dist/commands/ticket/move.d.ts +7 -0
- package/dist/commands/ticket/move.js +132 -0
- package/dist/commands/work/spawn.d.ts +1 -0
- package/dist/commands/work/spawn.js +72 -8
- package/dist/commands/work/start.js +6 -0
- package/dist/lib/execution/runners.js +21 -17
- package/dist/lib/execution/session-utils.d.ts +60 -0
- package/dist/lib/execution/session-utils.js +162 -0
- package/dist/lib/execution/spawner.d.ts +2 -0
- package/dist/lib/execution/spawner.js +42 -0
- package/dist/lib/flags/resolver.d.ts +2 -2
- package/dist/lib/flags/resolver.js +15 -0
- package/dist/lib/init/index.js +18 -0
- 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/multiline-input.d.ts +63 -0
- package/dist/lib/multiline-input.js +360 -0
- package/dist/lib/prompt-json.d.ts +57 -6
- package/dist/lib/prompt-json.js +45 -0
- package/dist/lib/repos/git.d.ts +7 -0
- package/dist/lib/repos/git.js +20 -0
- package/oclif.manifest.json +3690 -4995
- 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.js +0 -36
- 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.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
|
@@ -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
|
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
3
|
+
import { styles } from '../../lib/styles.js';
|
|
4
|
+
import { 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 LinkList extends PMOCommand {
|
|
19
|
+
static description = 'List all links (dependencies) for an entity';
|
|
20
|
+
static examples = [
|
|
21
|
+
'<%= config.bin %> <%= command.id %> TKT-001 # List ticket links',
|
|
22
|
+
'<%= config.bin %> <%= command.id %> SPEC-001 # List spec links',
|
|
23
|
+
'<%= config.bin %> <%= command.id %> EPIC-001 # List epic links',
|
|
24
|
+
'<%= config.bin %> <%= command.id %> TKT-001 --all # Include reverse links',
|
|
25
|
+
];
|
|
26
|
+
static args = {
|
|
27
|
+
id: Args.string({
|
|
28
|
+
description: 'Entity ID (TKT-xxx, SPEC-xxx, or EPIC-xxx)',
|
|
29
|
+
required: true,
|
|
30
|
+
}),
|
|
31
|
+
};
|
|
32
|
+
static flags = {
|
|
33
|
+
...pmoBaseFlags,
|
|
34
|
+
all: Flags.boolean({
|
|
35
|
+
char: 'a',
|
|
36
|
+
description: 'Show all links including reverse dependencies (what blocks this, what this blocks)',
|
|
37
|
+
default: false,
|
|
38
|
+
}),
|
|
39
|
+
json: Flags.boolean({
|
|
40
|
+
char: 'm',
|
|
41
|
+
aliases: ['machine'],
|
|
42
|
+
description: 'Output as JSON for AI agents/scripts',
|
|
43
|
+
default: false,
|
|
44
|
+
}),
|
|
45
|
+
};
|
|
46
|
+
async execute() {
|
|
47
|
+
const { args, flags } = await this.parse(LinkList);
|
|
48
|
+
const jsonMode = shouldOutputJson(flags);
|
|
49
|
+
const handleError = (code, message) => {
|
|
50
|
+
if (jsonMode) {
|
|
51
|
+
outputErrorAsJson(code, message, createMetadata('link list', flags));
|
|
52
|
+
this.exit(1);
|
|
53
|
+
}
|
|
54
|
+
this.error(message);
|
|
55
|
+
};
|
|
56
|
+
const entityType = inferEntityType(args.id);
|
|
57
|
+
if (!entityType) {
|
|
58
|
+
return handleError('INVALID_ID', `Cannot infer entity type from "${args.id}". Use TKT-, SPEC-, or EPIC- prefix.`);
|
|
59
|
+
}
|
|
60
|
+
if (entityType === 'ticket') {
|
|
61
|
+
await this.listTicketLinks(args.id, flags.all);
|
|
62
|
+
}
|
|
63
|
+
else if (entityType === 'spec') {
|
|
64
|
+
await this.listSpecLinks(args.id, flags.all);
|
|
65
|
+
}
|
|
66
|
+
else if (entityType === 'epic') {
|
|
67
|
+
await this.listEpicLinks(args.id, flags.all);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async listTicketLinks(ticketId, showAll) {
|
|
71
|
+
const ticket = await this.storage.getTicket(ticketId);
|
|
72
|
+
if (!ticket) {
|
|
73
|
+
this.error(`Ticket not found: ${ticketId}`);
|
|
74
|
+
}
|
|
75
|
+
const dependencies = await this.storage.listTicketDependencies(ticketId);
|
|
76
|
+
const blockers = await this.storage.getTicketBlockers(ticketId);
|
|
77
|
+
const isBlocked = await this.storage.isTicketBlocked(ticketId);
|
|
78
|
+
this.log(`\n${styles.emphasis(ticket.id)}: ${ticket.title}`);
|
|
79
|
+
if (isBlocked) {
|
|
80
|
+
this.log(styles.warning(' Status: BLOCKED'));
|
|
81
|
+
}
|
|
82
|
+
if (blockers.length > 0) {
|
|
83
|
+
this.log(styles.muted('\n Blocked by:'));
|
|
84
|
+
for (const blocker of blockers) {
|
|
85
|
+
const status = blocker.status === 'done' ? styles.success('done') : styles.warning(blocker.status);
|
|
86
|
+
this.log(` - ${blocker.id}: ${blocker.title} (${status})`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const otherDeps = dependencies.filter(d => d.dependencyType !== 'blocks');
|
|
90
|
+
if (otherDeps.length > 0) {
|
|
91
|
+
this.log(styles.muted('\n Related:'));
|
|
92
|
+
const relatedTickets = await Promise.all(otherDeps.map(async (dep) => ({
|
|
93
|
+
dep,
|
|
94
|
+
ticket: await this.storage.getTicket(dep.dependsOnTicketId)
|
|
95
|
+
})));
|
|
96
|
+
for (const { dep, ticket: relatedTicket } of relatedTickets) {
|
|
97
|
+
if (relatedTicket) {
|
|
98
|
+
this.log(` - ${dep.dependencyType}: ${relatedTicket.id} - ${relatedTicket.title}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (showAll) {
|
|
103
|
+
const blockedBy = await this.storage.getTicketsBlockedBy(ticketId);
|
|
104
|
+
if (blockedBy.length > 0) {
|
|
105
|
+
this.log(styles.muted('\n Blocking:'));
|
|
106
|
+
for (const blocked of blockedBy) {
|
|
107
|
+
this.log(` - ${blocked.id}: ${blocked.title} (${blocked.status})`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (dependencies.length === 0 && blockers.length === 0) {
|
|
112
|
+
this.log(styles.muted('\n No links.'));
|
|
113
|
+
}
|
|
114
|
+
this.log('');
|
|
115
|
+
}
|
|
116
|
+
async listSpecLinks(specId, showAll) {
|
|
117
|
+
const spec = await this.storage.getSpec(specId);
|
|
118
|
+
if (!spec) {
|
|
119
|
+
this.error(`Spec not found: ${specId}`);
|
|
120
|
+
}
|
|
121
|
+
const dependencies = await this.storage.listSpecDependencies(specId);
|
|
122
|
+
this.log(`\n${styles.emphasis(spec.id)}: ${spec.title}`);
|
|
123
|
+
if (dependencies.length === 0) {
|
|
124
|
+
this.log(styles.muted('\n No links.'));
|
|
125
|
+
this.log('');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
// Group by type
|
|
129
|
+
const byType = {};
|
|
130
|
+
for (const dep of dependencies) {
|
|
131
|
+
if (!byType[dep.dependencyType])
|
|
132
|
+
byType[dep.dependencyType] = [];
|
|
133
|
+
byType[dep.dependencyType].push(dep);
|
|
134
|
+
}
|
|
135
|
+
for (const [depType, deps] of Object.entries(byType)) {
|
|
136
|
+
this.log(styles.muted(`\n ${depType}:`));
|
|
137
|
+
for (const dep of deps) {
|
|
138
|
+
const targetSpec = await this.storage.getSpec(dep.dependsOnSpecId);
|
|
139
|
+
if (targetSpec) {
|
|
140
|
+
this.log(` - ${targetSpec.id}: ${targetSpec.title}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
this.log('');
|
|
145
|
+
}
|
|
146
|
+
async listEpicLinks(epicId, showAll) {
|
|
147
|
+
const epic = await this.storage.getEpic(epicId);
|
|
148
|
+
if (!epic) {
|
|
149
|
+
this.error(`Epic not found: ${epicId}`);
|
|
150
|
+
}
|
|
151
|
+
const dependencies = await this.storage.listEpicDependencies(epicId);
|
|
152
|
+
this.log(`\n${styles.emphasis(epic.id)}: ${epic.title}`);
|
|
153
|
+
// Show blocking dependencies
|
|
154
|
+
const blockingDeps = dependencies.filter(d => d.dependencyType === 'blocks');
|
|
155
|
+
if (blockingDeps.length > 0) {
|
|
156
|
+
this.log(styles.muted('\n Blocked by:'));
|
|
157
|
+
for (const dep of blockingDeps) {
|
|
158
|
+
const blocker = await this.storage.getEpic(dep.dependsOnEpicId);
|
|
159
|
+
if (blocker) {
|
|
160
|
+
const status = blocker.status === 'complete' ? styles.success('done') : styles.warning(blocker.status);
|
|
161
|
+
this.log(` - ${blocker.id}: ${blocker.title} (${status})`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const otherDeps = dependencies.filter(d => d.dependencyType !== 'blocks');
|
|
166
|
+
if (otherDeps.length > 0) {
|
|
167
|
+
this.log(styles.muted('\n Related:'));
|
|
168
|
+
for (const dep of otherDeps) {
|
|
169
|
+
const targetEpic = await this.storage.getEpic(dep.dependsOnEpicId);
|
|
170
|
+
if (targetEpic) {
|
|
171
|
+
this.log(` - ${dep.dependencyType}: ${targetEpic.id} - ${targetEpic.title}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// Note: showAll would show reverse dependencies, but we don't have getEpicsBlockedBy
|
|
176
|
+
// For now, we just show the epic's own dependencies
|
|
177
|
+
if (dependencies.length === 0) {
|
|
178
|
+
this.log(styles.muted('\n No links.'));
|
|
179
|
+
}
|
|
180
|
+
this.log('');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { PMOCommand } from '
|
|
2
|
-
export default class
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
export default class LinkRemove 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
10
|
type: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
-
all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
11
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
12
|
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
13
|
};
|
|
@@ -0,0 +1,120 @@
|
|
|
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 LinkRemove extends PMOCommand {
|
|
19
|
+
static description = 'Remove a link (dependency) between two entities';
|
|
20
|
+
static examples = [
|
|
21
|
+
'<%= config.bin %> <%= command.id %> TKT-001 TKT-002 # Remove link between tickets',
|
|
22
|
+
'<%= config.bin %> <%= command.id %> SPEC-001 SPEC-002 # Remove link between specs',
|
|
23
|
+
'<%= config.bin %> <%= command.id %> EPIC-001 EPIC-002 # Remove link between epics',
|
|
24
|
+
];
|
|
25
|
+
static args = {
|
|
26
|
+
from: Args.string({
|
|
27
|
+
description: 'Source entity ID (TKT-xxx, SPEC-xxx, or EPIC-xxx)',
|
|
28
|
+
required: true,
|
|
29
|
+
}),
|
|
30
|
+
to: Args.string({
|
|
31
|
+
description: 'Target entity ID',
|
|
32
|
+
required: true,
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
static flags = {
|
|
36
|
+
...pmoBaseFlags,
|
|
37
|
+
type: Flags.string({
|
|
38
|
+
char: 't',
|
|
39
|
+
description: 'Link type to remove (if not specified, removes any link)',
|
|
40
|
+
options: ['blocks', 'relates', 'duplicates', 'depends'],
|
|
41
|
+
}),
|
|
42
|
+
json: Flags.boolean({
|
|
43
|
+
char: 'm',
|
|
44
|
+
aliases: ['machine'],
|
|
45
|
+
description: 'Output prompt configuration as JSON (for AI agents/scripts)',
|
|
46
|
+
default: false,
|
|
47
|
+
}),
|
|
48
|
+
};
|
|
49
|
+
async execute() {
|
|
50
|
+
const { args, flags } = await this.parse(LinkRemove);
|
|
51
|
+
const jsonMode = shouldOutputJson(flags);
|
|
52
|
+
const handleError = (code, message) => {
|
|
53
|
+
if (jsonMode) {
|
|
54
|
+
outputErrorAsJson(code, message, createMetadata('link remove', flags));
|
|
55
|
+
this.exit(1);
|
|
56
|
+
}
|
|
57
|
+
this.error(message);
|
|
58
|
+
};
|
|
59
|
+
const fromType = inferEntityType(args.from);
|
|
60
|
+
const toType = inferEntityType(args.to);
|
|
61
|
+
if (!fromType) {
|
|
62
|
+
return handleError('INVALID_FROM_ID', `Cannot infer entity type from "${args.from}". Use TKT-, SPEC-, or EPIC- prefix.`);
|
|
63
|
+
}
|
|
64
|
+
if (!toType) {
|
|
65
|
+
return handleError('INVALID_TO_ID', `Cannot infer entity type from "${args.to}". Use TKT-, SPEC-, or EPIC- prefix.`);
|
|
66
|
+
}
|
|
67
|
+
if (fromType !== toType) {
|
|
68
|
+
return handleError('TYPE_MISMATCH', `Cannot link different entity types: ${fromType} and ${toType}`);
|
|
69
|
+
}
|
|
70
|
+
// Map the link type to the storage dependency type
|
|
71
|
+
const linkType = flags.type;
|
|
72
|
+
const dependencyType = linkType === 'relates' ? 'relates_to' :
|
|
73
|
+
linkType === 'depends' ? 'depends_on' : linkType;
|
|
74
|
+
try {
|
|
75
|
+
if (fromType === 'ticket') {
|
|
76
|
+
const ticket = await this.storage.getTicket(args.from);
|
|
77
|
+
if (!ticket)
|
|
78
|
+
return handleError('FROM_NOT_FOUND', `Ticket not found: ${args.from}`);
|
|
79
|
+
const targetTicket = await this.storage.getTicket(args.to);
|
|
80
|
+
if (!targetTicket)
|
|
81
|
+
return handleError('TO_NOT_FOUND', `Ticket not found: ${args.to}`);
|
|
82
|
+
await this.storage.deleteTicketDependency(args.from, args.to, dependencyType);
|
|
83
|
+
await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
|
|
84
|
+
this.log(styles.success(`\n✅ Link removed: ${styles.emphasis(args.from)} → ${styles.emphasis(args.to)}`));
|
|
85
|
+
this.log(styles.muted(` ${ticket.title} no longer linked to ${targetTicket.title}`));
|
|
86
|
+
}
|
|
87
|
+
else if (fromType === 'spec') {
|
|
88
|
+
const spec = await this.storage.getSpec(args.from);
|
|
89
|
+
if (!spec)
|
|
90
|
+
return handleError('FROM_NOT_FOUND', `Spec not found: ${args.from}`);
|
|
91
|
+
const targetSpec = await this.storage.getSpec(args.to);
|
|
92
|
+
if (!targetSpec)
|
|
93
|
+
return handleError('TO_NOT_FOUND', `Spec not found: ${args.to}`);
|
|
94
|
+
await this.storage.deleteSpecDependency(args.from, args.to, dependencyType);
|
|
95
|
+
this.log(styles.success(`\n✅ Link removed: ${styles.emphasis(args.from)} → ${styles.emphasis(args.to)}`));
|
|
96
|
+
this.log(styles.muted(` ${spec.title} no longer linked to ${targetSpec.title}`));
|
|
97
|
+
}
|
|
98
|
+
else if (fromType === 'epic') {
|
|
99
|
+
const epic = await this.storage.getEpic(args.from);
|
|
100
|
+
if (!epic)
|
|
101
|
+
return handleError('FROM_NOT_FOUND', `Epic not found: ${args.from}`);
|
|
102
|
+
const targetEpic = await this.storage.getEpic(args.to);
|
|
103
|
+
if (!targetEpic)
|
|
104
|
+
return handleError('TO_NOT_FOUND', `Epic not found: ${args.to}`);
|
|
105
|
+
await this.storage.deleteEpicDependency(args.from, args.to, dependencyType);
|
|
106
|
+
await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
|
|
107
|
+
this.log(styles.success(`\n✅ Link removed: ${styles.emphasis(args.from)} → ${styles.emphasis(args.to)}`));
|
|
108
|
+
this.log(styles.muted(` ${epic.title} no longer linked to ${targetEpic.title}`));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (error instanceof Error) {
|
|
113
|
+
if (error.message.includes('not found')) {
|
|
114
|
+
return handleError('LINK_NOT_FOUND', 'Link not found');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server Command
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive MCP (Model Context Protocol) server exposing ALL prlt commands
|
|
5
|
+
* as tools for AI agent integration.
|
|
6
|
+
*
|
|
7
|
+
* Usage in Claude Code config:
|
|
8
|
+
* ```json
|
|
9
|
+
* {
|
|
10
|
+
* "mcpServers": {
|
|
11
|
+
* "prlt": { "command": "prlt", "args": ["mcp-server"] }
|
|
12
|
+
* }
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
import { Command } from '@oclif/core';
|
|
17
|
+
export default class McpServerCommand extends Command {
|
|
18
|
+
static description: string;
|
|
19
|
+
static hidden: boolean;
|
|
20
|
+
static examples: string[];
|
|
21
|
+
run(): Promise<void>;
|
|
22
|
+
}
|