@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
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
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
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
18
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
19
|
+
import { execSync } from 'node:child_process';
|
|
20
|
+
import * as path from 'node:path';
|
|
21
|
+
import { getPMOContext } from '../lib/pmo/pmo-context.js';
|
|
22
|
+
import { registerTicketTools, registerProjectTools, registerBoardTools, registerSpecTools, registerEpicTools, registerWorkTools, registerWorkflowTools, registerStatusTools, registerPhaseTools, registerActionTools, registerRoadmapTools, registerCategoryTools, registerTemplateTools, registerViewTools, registerAgentTools, registerDockerTools, registerRepoTools, registerBranchTools, registerGitHubTools, registerInitTools, registerUtilityTools, } from '../lib/mcp/index.js';
|
|
23
|
+
export default class McpServerCommand extends Command {
|
|
24
|
+
static description = 'Start MCP server for AI agent integration (exposes all prlt commands as tools)';
|
|
25
|
+
static hidden = true;
|
|
26
|
+
static examples = ['<%= config.bin %> mcp-server'];
|
|
27
|
+
async run() {
|
|
28
|
+
// Initialize PMO context (may be null if not in a workspace)
|
|
29
|
+
let pmoContext = null;
|
|
30
|
+
try {
|
|
31
|
+
pmoContext = await getPMOContext({ logger: () => { } });
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
pmoContext = null;
|
|
35
|
+
}
|
|
36
|
+
// Create MCP server
|
|
37
|
+
const server = new McpServer({
|
|
38
|
+
name: 'prlt',
|
|
39
|
+
version: this.config.version,
|
|
40
|
+
});
|
|
41
|
+
// Create tool context
|
|
42
|
+
const ctx = {
|
|
43
|
+
get storage() {
|
|
44
|
+
if (!pmoContext) {
|
|
45
|
+
throw new Error('PMO not initialized. Run "prlt init" first.');
|
|
46
|
+
}
|
|
47
|
+
return pmoContext.storage;
|
|
48
|
+
},
|
|
49
|
+
runCommand: (cmd) => {
|
|
50
|
+
try {
|
|
51
|
+
const result = execSync(cmd, {
|
|
52
|
+
encoding: 'utf-8',
|
|
53
|
+
cwd: process.cwd(),
|
|
54
|
+
env: {
|
|
55
|
+
...process.env,
|
|
56
|
+
PATH: `${path.dirname(process.execPath)}:${process.env.PATH}`,
|
|
57
|
+
},
|
|
58
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
59
|
+
});
|
|
60
|
+
return result.trim();
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
const execError = error;
|
|
64
|
+
if (execError.stderr)
|
|
65
|
+
return execError.stderr.trim();
|
|
66
|
+
if (execError.stdout)
|
|
67
|
+
return execError.stdout.trim();
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
// Register all tool categories
|
|
73
|
+
registerTicketTools(server, ctx);
|
|
74
|
+
registerProjectTools(server, ctx);
|
|
75
|
+
registerBoardTools(server, ctx);
|
|
76
|
+
registerSpecTools(server, ctx);
|
|
77
|
+
registerEpicTools(server, ctx);
|
|
78
|
+
registerWorkTools(server, ctx);
|
|
79
|
+
registerWorkflowTools(server, ctx);
|
|
80
|
+
registerStatusTools(server, ctx);
|
|
81
|
+
registerPhaseTools(server, ctx);
|
|
82
|
+
registerActionTools(server, ctx);
|
|
83
|
+
registerRoadmapTools(server, ctx);
|
|
84
|
+
registerCategoryTools(server, ctx);
|
|
85
|
+
registerTemplateTools(server, ctx);
|
|
86
|
+
registerViewTools(server, ctx);
|
|
87
|
+
registerAgentTools(server, ctx);
|
|
88
|
+
registerDockerTools(server, ctx);
|
|
89
|
+
registerRepoTools(server, ctx);
|
|
90
|
+
registerBranchTools(server, ctx);
|
|
91
|
+
registerGitHubTools(server, ctx);
|
|
92
|
+
registerInitTools(server, ctx);
|
|
93
|
+
registerUtilityTools(server, ctx);
|
|
94
|
+
// Connect via stdio transport
|
|
95
|
+
const transport = new StdioServerTransport();
|
|
96
|
+
await server.connect(transport);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -21,7 +21,7 @@ export default class PhaseCreate extends PMOCommand {
|
|
|
21
21
|
...pmoBaseFlags,
|
|
22
22
|
category: Flags.string({
|
|
23
23
|
char: 'c',
|
|
24
|
-
description: 'State category',
|
|
24
|
+
description: 'State category [required for non-interactive]',
|
|
25
25
|
options: ['backlog', 'unstarted', 'started', 'completed', 'canceled'],
|
|
26
26
|
}),
|
|
27
27
|
color: Flags.string({
|
|
@@ -11,6 +11,7 @@ export default class ProjectCreate extends PMOCommand {
|
|
|
11
11
|
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
12
|
template: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
13
|
interactive: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
15
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
16
|
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
17
|
};
|
|
@@ -5,7 +5,7 @@ import inquirer from 'inquirer';
|
|
|
5
5
|
import { createBoardContent, createSpecFolders, PMOCommand, pmoBaseFlags, BUILTIN_TEMPLATES } from '../../lib/pmo/index.js';
|
|
6
6
|
import { styles } from '../../lib/styles.js';
|
|
7
7
|
import { slugify } from '../../lib/pmo/utils.js';
|
|
8
|
-
import { shouldOutputJson, outputPromptAsJson, outputSuccessAsJson, createMetadata, buildFormPromptConfig, } from '../../lib/prompt-json.js';
|
|
8
|
+
import { shouldOutputJson, outputPromptAsJson, outputSuccessAsJson, outputDryRunSuccessAsJson, outputDryRunErrorsAsJson, createMetadata, buildFormPromptConfig, } from '../../lib/prompt-json.js';
|
|
9
9
|
// Build template options dynamically from shared definitions
|
|
10
10
|
const TEMPLATE_IDS = BUILTIN_TEMPLATES.map(t => t.id);
|
|
11
11
|
export default class ProjectCreate extends PMOCommand {
|
|
@@ -14,6 +14,7 @@ export default class ProjectCreate extends PMOCommand {
|
|
|
14
14
|
'<%= config.bin %> <%= command.id %> "My New Project"',
|
|
15
15
|
'<%= config.bin %> <%= command.id %> --name "Mobile App" --description "iOS and Android app"',
|
|
16
16
|
'<%= config.bin %> <%= command.id %> -i # Interactive mode',
|
|
17
|
+
'<%= config.bin %> <%= command.id %> --name "Test" --dry-run --json # Validate without creating',
|
|
17
18
|
];
|
|
18
19
|
static args = {
|
|
19
20
|
name: Args.string({
|
|
@@ -25,7 +26,7 @@ export default class ProjectCreate extends PMOCommand {
|
|
|
25
26
|
...pmoBaseFlags,
|
|
26
27
|
name: Flags.string({
|
|
27
28
|
char: 'n',
|
|
28
|
-
description: 'Project name',
|
|
29
|
+
description: 'Project name [required for non-interactive]',
|
|
29
30
|
}),
|
|
30
31
|
id: Flags.string({
|
|
31
32
|
description: 'Custom project ID (auto-generated from name if not provided)',
|
|
@@ -45,6 +46,10 @@ export default class ProjectCreate extends PMOCommand {
|
|
|
45
46
|
description: 'Interactive mode',
|
|
46
47
|
default: false,
|
|
47
48
|
}),
|
|
49
|
+
'dry-run': Flags.boolean({
|
|
50
|
+
description: 'Validate inputs without creating project (use with --json for structured output)',
|
|
51
|
+
default: false,
|
|
52
|
+
}),
|
|
48
53
|
};
|
|
49
54
|
getPMOOptions() {
|
|
50
55
|
return { promptIfMultiple: false };
|
|
@@ -86,8 +91,39 @@ export default class ProjectCreate extends PMOCommand {
|
|
|
86
91
|
// Check if project already exists
|
|
87
92
|
const existing = await this.storage.getProject(projectId);
|
|
88
93
|
if (existing) {
|
|
94
|
+
if (flags['dry-run']) {
|
|
95
|
+
if (jsonMode) {
|
|
96
|
+
outputDryRunErrorsAsJson([{ field: 'id', error: `Project "${projectId}" already exists` }], createMetadata('project create', flags));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
89
99
|
this.error(`Project "${projectId}" already exists.`);
|
|
90
100
|
}
|
|
101
|
+
// Get the statuses from the workflow (for dry-run preview)
|
|
102
|
+
const statuses = await this.storage.listStatuses(projectData.template);
|
|
103
|
+
// Handle dry-run: show what would be created without actually creating
|
|
104
|
+
if (flags['dry-run']) {
|
|
105
|
+
const wouldCreate = {
|
|
106
|
+
id: projectId,
|
|
107
|
+
name: projectData.name,
|
|
108
|
+
template: projectData.template,
|
|
109
|
+
statuses: statuses.map(s => s.name),
|
|
110
|
+
...(projectData.description && { description: projectData.description }),
|
|
111
|
+
};
|
|
112
|
+
if (jsonMode) {
|
|
113
|
+
outputDryRunSuccessAsJson('project', wouldCreate, createMetadata('project create', flags));
|
|
114
|
+
}
|
|
115
|
+
// Human-readable dry-run output
|
|
116
|
+
this.log(styles.warning('\n[DRY RUN] Would create project:'));
|
|
117
|
+
this.log(styles.muted(` ID: ${projectId}`));
|
|
118
|
+
this.log(styles.muted(` Name: ${projectData.name}`));
|
|
119
|
+
this.log(styles.muted(` Template: ${projectData.template}`));
|
|
120
|
+
this.log(styles.muted(` Statuses: ${statuses.map(s => s.name).join(' → ')}`));
|
|
121
|
+
if (projectData.description) {
|
|
122
|
+
this.log(styles.muted(` Description: ${projectData.description}`));
|
|
123
|
+
}
|
|
124
|
+
this.log(styles.muted('\n(No project was created)'));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
91
127
|
// Create project in database
|
|
92
128
|
const project = await this.storage.createProject({
|
|
93
129
|
id: projectId,
|
|
@@ -104,8 +140,6 @@ export default class ProjectCreate extends PMOCommand {
|
|
|
104
140
|
fs.writeFileSync(boardPath, boardContent);
|
|
105
141
|
// Create spec folders in project directory
|
|
106
142
|
const specsPath = createSpecFolders(this.pmoPath, projectId);
|
|
107
|
-
// Get the statuses from the workflow (template name = workflow ID for built-in templates)
|
|
108
|
-
const statuses = await this.storage.listStatuses(projectData.template);
|
|
109
143
|
// In JSON mode, output success with project details
|
|
110
144
|
if (jsonMode) {
|
|
111
145
|
outputSuccessAsJson({
|
|
@@ -11,6 +11,7 @@ export default class SpecCreate extends PMOCommand {
|
|
|
11
11
|
type: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
12
|
problem: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
13
|
interactive: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
15
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
16
|
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
17
|
};
|
|
@@ -2,7 +2,7 @@ import { Flags, Args } from '@oclif/core';
|
|
|
2
2
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
3
3
|
import { styles } from '../../lib/styles.js';
|
|
4
4
|
import { slugify } from '../../lib/pmo/utils.js';
|
|
5
|
-
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
5
|
+
import { shouldOutputJson, outputDryRunSuccessAsJson, outputDryRunErrorsAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
6
6
|
import { FlagResolver } from '../../lib/flags/index.js';
|
|
7
7
|
export default class SpecCreate extends PMOCommand {
|
|
8
8
|
static description = 'Create a new spec';
|
|
@@ -10,6 +10,7 @@ export default class SpecCreate extends PMOCommand {
|
|
|
10
10
|
'<%= config.bin %> <%= command.id %> "User Authentication"',
|
|
11
11
|
'<%= config.bin %> <%= command.id %> --title "API Design" --type product',
|
|
12
12
|
'<%= config.bin %> <%= command.id %> -i # Interactive mode',
|
|
13
|
+
'<%= config.bin %> <%= command.id %> --title "Test" --dry-run --json # Validate without creating',
|
|
13
14
|
];
|
|
14
15
|
static args = {
|
|
15
16
|
title: Args.string({
|
|
@@ -21,7 +22,7 @@ export default class SpecCreate extends PMOCommand {
|
|
|
21
22
|
...pmoBaseFlags,
|
|
22
23
|
title: Flags.string({
|
|
23
24
|
char: 't',
|
|
24
|
-
description: 'Spec title',
|
|
25
|
+
description: 'Spec title [required for non-interactive]',
|
|
25
26
|
}),
|
|
26
27
|
status: Flags.string({
|
|
27
28
|
char: 's',
|
|
@@ -41,6 +42,10 @@ export default class SpecCreate extends PMOCommand {
|
|
|
41
42
|
description: 'Interactive mode',
|
|
42
43
|
default: false,
|
|
43
44
|
}),
|
|
45
|
+
'dry-run': Flags.boolean({
|
|
46
|
+
description: 'Validate inputs without creating spec (use with --json for structured output)',
|
|
47
|
+
default: false,
|
|
48
|
+
}),
|
|
44
49
|
};
|
|
45
50
|
async execute() {
|
|
46
51
|
const { args, flags } = await this.parse(SpecCreate);
|
|
@@ -108,6 +113,42 @@ export default class SpecCreate extends PMOCommand {
|
|
|
108
113
|
const specType = resolved.type === '' ? undefined : resolved.type;
|
|
109
114
|
// Generate ID from title
|
|
110
115
|
const specId = slugify(resolved.title);
|
|
116
|
+
// Check if spec already exists
|
|
117
|
+
const existing = await this.storage.getSpec(specId);
|
|
118
|
+
if (existing) {
|
|
119
|
+
if (flags['dry-run']) {
|
|
120
|
+
if (jsonMode) {
|
|
121
|
+
outputDryRunErrorsAsJson([{ field: 'id', error: `Spec "${specId}" already exists` }], createMetadata('spec create', flags));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
this.error(`Spec "${specId}" already exists.`);
|
|
125
|
+
}
|
|
126
|
+
// Handle dry-run: show what would be created without actually creating
|
|
127
|
+
if (flags['dry-run']) {
|
|
128
|
+
const wouldCreate = {
|
|
129
|
+
id: specId,
|
|
130
|
+
title: resolved.title,
|
|
131
|
+
status: resolved.status || 'draft',
|
|
132
|
+
...(specType && { type: specType }),
|
|
133
|
+
...(resolved.problem && { problem: resolved.problem }),
|
|
134
|
+
};
|
|
135
|
+
if (jsonMode) {
|
|
136
|
+
outputDryRunSuccessAsJson('spec', wouldCreate, createMetadata('spec create', flags));
|
|
137
|
+
}
|
|
138
|
+
// Human-readable dry-run output
|
|
139
|
+
this.log(styles.warning('\n[DRY RUN] Would create spec:'));
|
|
140
|
+
this.log(styles.muted(` ID: ${specId}`));
|
|
141
|
+
this.log(styles.muted(` Title: ${resolved.title}`));
|
|
142
|
+
this.log(styles.muted(` Status: ${resolved.status || 'draft'}`));
|
|
143
|
+
if (specType) {
|
|
144
|
+
this.log(styles.muted(` Type: ${specType}`));
|
|
145
|
+
}
|
|
146
|
+
if (resolved.problem) {
|
|
147
|
+
this.log(styles.muted(` Problem: ${resolved.problem}`));
|
|
148
|
+
}
|
|
149
|
+
this.log(styles.muted('\n(No spec was created)'));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
111
152
|
// Create spec in database
|
|
112
153
|
const spec = await this.storage.createSpec({
|
|
113
154
|
id: specId,
|
|
@@ -48,7 +48,7 @@ export default class Spec extends PMOCommand {
|
|
|
48
48
|
delete: 'prlt spec delete --json',
|
|
49
49
|
generate: 'prlt spec plan --json',
|
|
50
50
|
ticket: 'prlt spec ticket --json',
|
|
51
|
-
link: 'prlt
|
|
51
|
+
link: 'prlt link list --json',
|
|
52
52
|
cancel: '',
|
|
53
53
|
};
|
|
54
54
|
return commands[value] || '';
|
|
@@ -80,7 +80,7 @@ export default class Spec extends PMOCommand {
|
|
|
80
80
|
await this.config.runCommand('spec:ticket', []);
|
|
81
81
|
break;
|
|
82
82
|
case 'link':
|
|
83
|
-
await this.config.runCommand('
|
|
83
|
+
await this.config.runCommand('link', []);
|
|
84
84
|
break;
|
|
85
85
|
}
|
|
86
86
|
}
|