@proletariat/cli 0.3.34 → 0.3.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/dist/commands/agent/auth.d.ts +15 -3
  2. package/dist/commands/agent/auth.js +136 -15
  3. package/dist/commands/agent/index.js +11 -2
  4. package/dist/commands/agent/list.js +16 -7
  5. package/dist/commands/agent/staff/add.d.ts +1 -0
  6. package/dist/commands/agent/staff/add.js +1 -0
  7. package/dist/commands/agent/staff/index.d.ts +15 -0
  8. package/dist/commands/agent/staff/index.js +83 -0
  9. package/dist/commands/agent/staff/list.d.ts +1 -0
  10. package/dist/commands/agent/staff/list.js +1 -0
  11. package/dist/commands/agent/staff/remove.d.ts +1 -0
  12. package/dist/commands/agent/staff/remove.js +1 -0
  13. package/dist/commands/agent/status.js +32 -4
  14. package/dist/commands/agent/themes/add-names.d.ts +1 -0
  15. package/dist/commands/agent/themes/add-names.js +1 -0
  16. package/dist/commands/agent/themes/create.d.ts +1 -0
  17. package/dist/commands/agent/themes/create.js +1 -0
  18. package/dist/commands/agent/themes/index.d.ts +10 -0
  19. package/dist/commands/agent/themes/index.js +144 -0
  20. package/dist/commands/agent/themes/list.d.ts +1 -0
  21. package/dist/commands/agent/themes/list.js +1 -0
  22. package/dist/commands/agent/themes/set.d.ts +1 -0
  23. package/dist/commands/agent/themes/set.js +1 -0
  24. package/dist/commands/agents/themes/add-names.d.ts +1 -0
  25. package/dist/commands/agents/themes/add-names.js +1 -0
  26. package/dist/commands/agents/themes/create.d.ts +1 -0
  27. package/dist/commands/agents/themes/create.js +1 -0
  28. package/dist/commands/agents/themes/list.d.ts +1 -0
  29. package/dist/commands/agents/themes/list.js +1 -0
  30. package/dist/commands/board/watch.js +6 -0
  31. package/dist/commands/branch/list.d.ts +1 -0
  32. package/dist/commands/branch/list.js +43 -12
  33. package/dist/commands/branch/where.js +3 -2
  34. package/dist/commands/category/list.d.ts +2 -1
  35. package/dist/commands/category/list.js +38 -13
  36. package/dist/commands/{claude.d.ts → claude/index.d.ts} +1 -1
  37. package/dist/commands/{claude.js → claude/index.js} +12 -12
  38. package/dist/commands/claude/open.d.ts +13 -0
  39. package/dist/commands/claude/open.js +175 -0
  40. package/dist/commands/diet.js +18 -2
  41. package/dist/commands/docker/logs.js +7 -3
  42. package/dist/commands/docker/shell.js +6 -0
  43. package/dist/commands/docker/start.js +20 -4
  44. package/dist/commands/docker/sync.d.ts +4 -0
  45. package/dist/commands/docker/sync.js +30 -2
  46. package/dist/commands/epic/show.d.ts +13 -0
  47. package/dist/commands/epic/show.js +16 -0
  48. package/dist/commands/epic/view.js +27 -0
  49. package/dist/commands/execution/config.d.ts +0 -4
  50. package/dist/commands/execution/config.js +10 -32
  51. package/dist/commands/execution/index.js +2 -1
  52. package/dist/commands/execution/logs.js +1 -1
  53. package/dist/commands/execution/stop.js +2 -1
  54. package/dist/commands/execution/view.js +22 -26
  55. package/dist/commands/init.js +2 -19
  56. package/dist/commands/label/create.d.ts +20 -0
  57. package/dist/commands/label/create.js +57 -0
  58. package/dist/commands/label/delete.d.ts +17 -0
  59. package/dist/commands/label/delete.js +32 -0
  60. package/dist/commands/label/group/create.d.ts +20 -0
  61. package/dist/commands/label/group/create.js +55 -0
  62. package/dist/commands/label/group/list.d.ts +14 -0
  63. package/dist/commands/label/group/list.js +52 -0
  64. package/dist/commands/label/index.d.ts +15 -0
  65. package/dist/commands/label/index.js +58 -0
  66. package/dist/commands/label/list.d.ts +16 -0
  67. package/dist/commands/label/list.js +83 -0
  68. package/dist/commands/link/list.js +3 -2
  69. package/dist/commands/mcp-server.js +27 -1
  70. package/dist/commands/phase/template/apply.d.ts +26 -0
  71. package/dist/commands/phase/template/apply.js +14 -0
  72. package/dist/commands/phase/template/create.d.ts +23 -0
  73. package/dist/commands/phase/template/create.js +14 -0
  74. package/dist/commands/phase/template/delete.d.ts +18 -0
  75. package/dist/commands/phase/template/delete.js +61 -0
  76. package/dist/commands/phase/template/list.d.ts +17 -0
  77. package/dist/commands/phase/template/list.js +89 -0
  78. package/dist/commands/phase/template/update.d.ts +1 -0
  79. package/dist/commands/phase/template/update.js +1 -0
  80. package/dist/commands/priority/add.js +1 -1
  81. package/dist/commands/project/create.js +3 -4
  82. package/dist/commands/project/update.js +5 -8
  83. package/dist/commands/pull.js +24 -0
  84. package/dist/commands/roadmap/generate.js +1 -2
  85. package/dist/commands/session/create.d.ts +19 -0
  86. package/dist/commands/session/create.js +102 -0
  87. package/dist/commands/session/health.js +2 -21
  88. package/dist/commands/session/index.js +14 -1
  89. package/dist/commands/session/list.js +26 -7
  90. package/dist/commands/session/peek.d.ts +38 -0
  91. package/dist/commands/session/peek.js +316 -0
  92. package/dist/commands/session/poke.d.ts +27 -0
  93. package/dist/commands/session/poke.js +219 -0
  94. package/dist/commands/spec/link/depends.d.ts +18 -0
  95. package/dist/commands/spec/link/depends.js +86 -0
  96. package/dist/commands/spec/link/index.d.ts +17 -0
  97. package/dist/commands/spec/link/index.js +92 -0
  98. package/dist/commands/spec/link/remove.d.ts +18 -0
  99. package/dist/commands/spec/link/remove.js +90 -0
  100. package/dist/commands/spec/view.js +29 -0
  101. package/dist/commands/support/logs.js +2 -2
  102. package/dist/commands/template/apply.js +5 -4
  103. package/dist/commands/template/create.js +1 -1
  104. package/dist/commands/template/list.js +2 -1
  105. package/dist/commands/theme/add-names.d.ts +4 -0
  106. package/dist/commands/theme/add-names.js +11 -1
  107. package/dist/commands/theme/create.d.ts +2 -0
  108. package/dist/commands/theme/create.js +8 -0
  109. package/dist/commands/ticket/bulk.js +2 -2
  110. package/dist/commands/ticket/complete.js +2 -2
  111. package/dist/commands/ticket/create.js +21 -0
  112. package/dist/commands/ticket/delete.js +8 -0
  113. package/dist/commands/ticket/edit.js +25 -0
  114. package/dist/commands/ticket/index.js +2 -2
  115. package/dist/commands/ticket/link/block.d.ts +15 -0
  116. package/dist/commands/ticket/link/block.js +95 -0
  117. package/dist/commands/ticket/link/index.d.ts +14 -0
  118. package/dist/commands/ticket/link/index.js +96 -0
  119. package/dist/commands/ticket/list.d.ts +1 -0
  120. package/dist/commands/ticket/list.js +6 -0
  121. package/dist/commands/ticket/move.js +25 -2
  122. package/dist/commands/ticket/resolve.js +4 -5
  123. package/dist/commands/ticket/show.d.ts +13 -0
  124. package/dist/commands/ticket/show.js +16 -0
  125. package/dist/commands/ticket/template/apply.d.ts +26 -0
  126. package/dist/commands/ticket/template/apply.js +14 -0
  127. package/dist/commands/ticket/template/delete.d.ts +18 -0
  128. package/dist/commands/ticket/template/delete.js +61 -0
  129. package/dist/commands/ticket/template/list.d.ts +17 -0
  130. package/dist/commands/ticket/template/list.js +78 -0
  131. package/dist/commands/ticket/template/save.d.ts +17 -0
  132. package/dist/commands/ticket/template/save.js +97 -0
  133. package/dist/commands/ticket/view.js +30 -0
  134. package/dist/commands/work/index.js +4 -0
  135. package/dist/commands/work/ready.js +17 -0
  136. package/dist/commands/work/resolve.js +1 -1
  137. package/dist/commands/work/spawn.js +4 -4
  138. package/dist/commands/work/start.d.ts +1 -0
  139. package/dist/commands/work/start.js +203 -93
  140. package/dist/commands/work/status.d.ts +14 -0
  141. package/dist/commands/work/status.js +60 -0
  142. package/dist/commands/workflow/index.js +2 -1
  143. package/dist/commands/workflow/show.d.ts +13 -0
  144. package/dist/commands/workflow/show.js +16 -0
  145. package/dist/commands/workspace/add.js +15 -0
  146. package/dist/commands/workspace/list.js +2 -1
  147. package/dist/commands/workspace/prune.js +5 -5
  148. package/dist/lib/branch/index.d.ts +1 -0
  149. package/dist/lib/database/index.d.ts +1 -1
  150. package/dist/lib/database/index.js +20 -0
  151. package/dist/lib/execution/config.d.ts +15 -1
  152. package/dist/lib/execution/config.js +28 -0
  153. package/dist/lib/execution/devcontainer.js +3 -1
  154. package/dist/lib/execution/runners.d.ts +18 -2
  155. package/dist/lib/execution/runners.js +71 -29
  156. package/dist/lib/execution/session-utils.d.ts +11 -1
  157. package/dist/lib/execution/session-utils.js +26 -1
  158. package/dist/lib/execution/storage.d.ts +5 -0
  159. package/dist/lib/execution/storage.js +18 -3
  160. package/dist/lib/execution/types.d.ts +3 -0
  161. package/dist/lib/flags/resolver.js +1 -0
  162. package/dist/lib/mcp/helpers.d.ts +1 -2
  163. package/dist/lib/mcp/tools/board.js +4 -6
  164. package/dist/lib/mcp/tools/cli-passthrough.js +25 -6
  165. package/dist/lib/mcp/tools/diet.js +1 -0
  166. package/dist/lib/mcp/tools/epic.js +8 -3
  167. package/dist/lib/mcp/tools/index.d.ts +1 -0
  168. package/dist/lib/mcp/tools/index.js +1 -0
  169. package/dist/lib/mcp/tools/label.d.ts +6 -0
  170. package/dist/lib/mcp/tools/label.js +338 -0
  171. package/dist/lib/mcp/tools/spec.js +1 -1
  172. package/dist/lib/mcp/tools/ticket.js +57 -19
  173. package/dist/lib/mcp/tools/work.js +96 -6
  174. package/dist/lib/mcp/types.d.ts +10 -0
  175. package/dist/lib/multiline-input.js +8 -19
  176. package/dist/lib/pmo/base-command.d.ts +0 -1
  177. package/dist/lib/pmo/base-command.js +4 -5
  178. package/dist/lib/pmo/schema.d.ts +6 -0
  179. package/dist/lib/pmo/schema.js +44 -0
  180. package/dist/lib/pmo/storage/actions.js +1 -1
  181. package/dist/lib/pmo/storage/base.d.ts +6 -0
  182. package/dist/lib/pmo/storage/base.js +311 -52
  183. package/dist/lib/pmo/storage/index.d.ts +23 -1
  184. package/dist/lib/pmo/storage/index.js +59 -1
  185. package/dist/lib/pmo/storage/labels.d.ts +55 -0
  186. package/dist/lib/pmo/storage/labels.js +346 -0
  187. package/dist/lib/pmo/storage/tickets.js +17 -0
  188. package/dist/lib/pmo/storage/types.d.ts +25 -0
  189. package/dist/lib/pmo/types.d.ts +44 -0
  190. package/dist/lib/pmo/utils.js +1 -1
  191. package/dist/lib/prompt-command.d.ts +20 -0
  192. package/dist/lib/prompt-command.js +38 -2
  193. package/dist/lib/prompt-json.d.ts +36 -4
  194. package/dist/lib/prompt-json.js +129 -7
  195. package/dist/lib/styles.d.ts +37 -0
  196. package/dist/lib/styles.js +73 -0
  197. package/oclif.manifest.json +6399 -3799
  198. package/package.json +1 -1
@@ -5,7 +5,7 @@ import { z } from 'zod';
5
5
  import { formatTicket, formatTicketFull, errorResponse, strictTool } from '../helpers.js';
6
6
  import { getWorkspacePriorities, setWorkspacePriorities } from '../../pmo/utils.js';
7
7
  export function registerTicketTools(server, ctx) {
8
- strictTool(server, 'ticket_list', 'List tickets with optional filters', {
8
+ strictTool(server, 'ticket_list', 'List tickets with optional filters. Returns summary fields only (no descriptions). Use ticket_show for full details.', {
9
9
  project: z.string().optional().describe('Project ID'),
10
10
  column: z.string().optional().describe('Filter by column/status'),
11
11
  priority: z.string().optional().describe('Filter by priority (uses workspace priority scale)'),
@@ -14,10 +14,14 @@ export function registerTicketTools(server, ctx) {
14
14
  owner: z.string().optional().describe('Filter by owner'),
15
15
  search: z.string().optional().describe('Search in title/description'),
16
16
  epic: z.string().optional().describe('Filter by epic ID'),
17
+ label: z.string().optional().describe('Filter by label name'),
18
+ label_group: z.string().optional().describe('Filter by label group name'),
17
19
  all_projects: z.boolean().optional().describe('List from all projects'),
20
+ limit: z.number().min(1).optional().describe('Maximum number of tickets to return (default: 50)'),
21
+ offset: z.number().min(0).optional().describe('Number of tickets to skip for pagination (default: 0)'),
18
22
  }, async (params) => {
19
23
  try {
20
- const tickets = await ctx.storage.listTickets(params.all_projects ? undefined : params.project, {
24
+ const allTickets = await ctx.storage.listTickets(params.all_projects ? undefined : params.project, {
21
25
  column: params.column,
22
26
  priority: params.priority,
23
27
  category: params.category,
@@ -25,30 +29,36 @@ export function registerTicketTools(server, ctx) {
25
29
  owner: params.owner,
26
30
  search: params.search,
27
31
  epic: params.epic,
32
+ label: params.label,
33
+ labelGroup: params.label_group,
28
34
  allProjects: params.all_projects,
29
35
  });
36
+ const total = allTickets.length;
37
+ const offset = params.offset ?? 0;
38
+ const limit = params.limit ?? 50;
39
+ const tickets = allTickets.slice(offset, offset + limit);
30
40
  return {
31
41
  content: [{
32
42
  type: 'text',
33
43
  text: JSON.stringify({
34
44
  success: true,
45
+ total,
35
46
  count: tickets.length,
36
- tickets: tickets.map((t) => ({
37
- id: t.id,
38
- title: t.title,
39
- description: t.description,
40
- priority: t.priority,
41
- category: t.category,
42
- statusName: t.statusName,
43
- statusCategory: t.statusCategory,
44
- projectId: t.projectId,
45
- assignee: t.assignee,
46
- owner: t.owner,
47
- epicId: t.epicId,
48
- branch: t.branch,
49
- position: t.position,
50
- createdAt: t.createdAt.toISOString(),
51
- updatedAt: t.updatedAt.toISOString(),
47
+ offset,
48
+ limit,
49
+ tickets: await Promise.all(tickets.map(async (t) => {
50
+ const ticketLabels = await ctx.storage.getLabelsForTicket(t.id);
51
+ return {
52
+ id: t.id,
53
+ title: t.title,
54
+ priority: t.priority,
55
+ category: t.category,
56
+ statusName: t.statusName,
57
+ statusCategory: t.statusCategory,
58
+ assignee: t.assignee,
59
+ position: t.position,
60
+ labels: ticketLabels.map(l => ({ id: l.id, name: l.name, groupName: l.groupName })),
61
+ };
52
62
  })),
53
63
  }, null, 2),
54
64
  }],
@@ -91,6 +101,24 @@ export function registerTicketTools(server, ctx) {
91
101
  labels: params.labels,
92
102
  subtasks: params.subtasks?.map((title) => ({ id: '', title, done: false })),
93
103
  });
104
+ // Add structured labels from the labels param via junction table
105
+ if (params.labels && params.labels.length > 0) {
106
+ for (const labelName of params.labels) {
107
+ try {
108
+ // Try to add as structured label (by name or ID)
109
+ const labelById = await ctx.storage.getLabel(labelName);
110
+ if (labelById) {
111
+ await ctx.storage.addLabelToTicket(ticket.id, labelById.id);
112
+ }
113
+ else {
114
+ await ctx.storage.addLabelToTicketByName(ticket.id, labelName);
115
+ }
116
+ }
117
+ catch {
118
+ // If label doesn't exist in the label system, it stays only in the legacy JSON array
119
+ }
120
+ }
121
+ }
94
122
  return {
95
123
  content: [{
96
124
  type: 'text',
@@ -107,10 +135,20 @@ export function registerTicketTools(server, ctx) {
107
135
  const ticket = await ctx.storage.getTicket(params.id);
108
136
  if (!ticket)
109
137
  throw new Error(`Ticket not found: ${params.id}`);
138
+ const ticketLabels = await ctx.storage.getLabelsForTicket(params.id);
139
+ const ticketData = formatTicketFull(ticket);
140
+ // Override labels with structured label data from junction table
141
+ ticketData.labels = ticketLabels.map(l => ({
142
+ id: l.id,
143
+ name: l.name,
144
+ color: l.color,
145
+ groupId: l.groupId,
146
+ groupName: l.groupName,
147
+ }));
110
148
  return {
111
149
  content: [{
112
150
  type: 'text',
113
- text: JSON.stringify({ success: true, ticket: formatTicketFull(ticket) }, null, 2),
151
+ text: JSON.stringify({ success: true, ticket: ticketData }, null, 2),
114
152
  }],
115
153
  };
116
154
  }
@@ -3,6 +3,8 @@
3
3
  */
4
4
  import { z } from 'zod';
5
5
  import { formatTicket, errorResponse, strictTool } from '../helpers.js';
6
+ import { spawnAgentForTicket } from '../../execution/spawner.js';
7
+ import { createEphemeralAgent } from '../../agents/commands.js';
6
8
  export function registerWorkTools(server, ctx) {
7
9
  strictTool(server, 'work_status', 'Get current work status (in-progress tickets)', {}, async () => {
8
10
  try {
@@ -31,7 +33,7 @@ export function registerWorkTools(server, ctx) {
31
33
  return errorResponse(error);
32
34
  }
33
35
  });
34
- strictTool(server, 'work_start', 'Start working on a ticket (moves to In Progress)', {
36
+ strictTool(server, 'work_assign', 'Assign a ticket to someone for work preparation (does NOT move to In Progress — use the CLI "work start" command to actually start work with an agent/session)', {
35
37
  ticket_id: z.string().describe('Ticket ID'),
36
38
  assignee: z.string().optional().describe('Who is working'),
37
39
  }, async (params) => {
@@ -42,16 +44,14 @@ export function registerWorkTools(server, ctx) {
42
44
  if (params.assignee) {
43
45
  await ctx.storage.updateTicket(params.ticket_id, { assignee: params.assignee });
44
46
  }
45
- const columns = ctx.storage.getColumnNames(ticket.projectId);
46
- const progressCol = columns.find((c) => c.toLowerCase().includes('progress') || c.toLowerCase().includes('doing')) || columns[Math.min(1, columns.length - 1)];
47
- const moved = await ctx.storage.moveTicket(ticket.projectId, params.ticket_id, progressCol);
47
+ const updated = await ctx.storage.getTicket(params.ticket_id);
48
48
  return {
49
49
  content: [{
50
50
  type: 'text',
51
51
  text: JSON.stringify({
52
52
  success: true,
53
- ticket: formatTicket(moved),
54
- message: `Started ${moved.id}: ${moved.title}`,
53
+ ticket: formatTicket(updated),
54
+ message: `Assigned ${updated.id}: ${updated.title}${params.assignee ? ` to ${params.assignee}` : ''}`,
55
55
  }, null, 2),
56
56
  }],
57
57
  };
@@ -129,4 +129,94 @@ export function registerWorkTools(server, ctx) {
129
129
  return errorResponse(error);
130
130
  }
131
131
  });
132
+ strictTool(server, 'work_start', 'Start work on a ticket — spawns an agent with a running container/session, creates a git branch, and moves ticket to In Progress only after successful spawn', {
133
+ ticket_id: z.string().describe('Ticket ID to start work on'),
134
+ agent: z.string().optional().describe('Agent name to assign (defaults to creating an ephemeral agent)'),
135
+ environment: z.enum(['devcontainer', 'host']).optional().describe('Execution environment (default: devcontainer if available)'),
136
+ display_mode: z.enum(['background', 'terminal']).optional().describe('Display mode (default: background — MCP runs headless)'),
137
+ skip_permissions: z.boolean().optional().describe('Skip permission prompts (danger mode, default: false)'),
138
+ create_pr: z.boolean().optional().describe('Create PR when work is ready (default: false)'),
139
+ }, async (params) => {
140
+ try {
141
+ // Require workspace context for spawn operations
142
+ if (!ctx.getWorkspaceContext) {
143
+ throw new Error('Workspace not initialized. work_start requires a workspace with agents. Run "prlt init" first.');
144
+ }
145
+ const { workspaceInfo, executionStorage, db, pmoPath } = ctx.getWorkspaceContext();
146
+ // Validate ticket exists
147
+ const ticket = await ctx.storage.getTicket(params.ticket_id);
148
+ if (!ticket)
149
+ throw new Error(`Ticket not found: ${params.ticket_id}`);
150
+ // Check ticket is not blocked
151
+ if (ticket.blockedBy && ticket.blockedBy.length > 0) {
152
+ throw new Error(`Ticket ${ticket.id} is blocked by: ${ticket.blockedBy.join(', ')}. Resolve blockers first.`);
153
+ }
154
+ // Check for existing running execution
155
+ const runningExec = executionStorage.getRunningExecution(ticket.id);
156
+ if (runningExec) {
157
+ throw new Error(`Ticket ${ticket.id} already has a running execution: ${runningExec.id} (agent: ${runningExec.agentName})`);
158
+ }
159
+ // Select or create agent
160
+ let agentName;
161
+ if (params.agent) {
162
+ // Use specified agent — verify it exists
163
+ const agentExists = workspaceInfo.agents.some(a => a.name === params.agent);
164
+ if (!agentExists) {
165
+ throw new Error(`Agent "${params.agent}" not found. Available agents: ${workspaceInfo.agents.map(a => a.name).join(', ') || 'none'}`);
166
+ }
167
+ agentName = params.agent;
168
+ }
169
+ else {
170
+ // Create an ephemeral agent (default for MCP)
171
+ const ephemeralResult = await createEphemeralAgent(workspaceInfo);
172
+ agentName = ephemeralResult.name;
173
+ }
174
+ // Spawn the agent with background mode (MCP is headless)
175
+ const displayMode = params.display_mode || 'background';
176
+ const environment = params.environment;
177
+ const result = await spawnAgentForTicket(ticket, agentName, ctx.storage, executionStorage, workspaceInfo, db, pmoPath, {
178
+ environment,
179
+ displayMode,
180
+ skipPermissions: params.skip_permissions ?? false,
181
+ createPR: params.create_pr ?? false,
182
+ skipRemoteCheck: false,
183
+ });
184
+ if (result.success) {
185
+ // Fetch updated ticket to return current state
186
+ const updatedTicket = await ctx.storage.getTicket(params.ticket_id);
187
+ return {
188
+ content: [{
189
+ type: 'text',
190
+ text: JSON.stringify({
191
+ success: true,
192
+ executionId: result.executionId,
193
+ ticketId: result.ticketId,
194
+ agent: result.agentName,
195
+ status: 'running',
196
+ ticket: updatedTicket ? formatTicket(updatedTicket) : undefined,
197
+ message: `Started work on ${ticket.id}: ${ticket.title} (agent: ${agentName})`,
198
+ }, null, 2),
199
+ }],
200
+ };
201
+ }
202
+ else {
203
+ return {
204
+ content: [{
205
+ type: 'text',
206
+ text: JSON.stringify({
207
+ success: false,
208
+ ticketId: result.ticketId,
209
+ agent: result.agentName,
210
+ error: result.error || 'Spawn failed',
211
+ message: `Failed to start work on ${ticket.id}: ${result.error}`,
212
+ }, null, 2),
213
+ }],
214
+ isError: true,
215
+ };
216
+ }
217
+ }
218
+ catch (error) {
219
+ return errorResponse(error);
220
+ }
221
+ });
132
222
  }
@@ -3,9 +3,19 @@
3
3
  */
4
4
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
5
5
  import type { SQLiteStorage } from '../pmo/storage/index.js';
6
+ import type Database from 'better-sqlite3';
7
+ import type { WorkspaceInfo } from '../agents/commands.js';
8
+ import type { ExecutionStorage } from '../execution/storage.js';
6
9
  export interface McpToolContext {
7
10
  storage: SQLiteStorage;
8
11
  runCommand: (cmd: string) => string;
12
+ /** Workspace info — available when running in a workspace with agents */
13
+ getWorkspaceContext?: () => {
14
+ workspaceInfo: WorkspaceInfo;
15
+ executionStorage: ExecutionStorage;
16
+ db: Database.Database;
17
+ pmoPath: string;
18
+ };
9
19
  }
10
20
  export type McpToolResult = {
11
21
  content: Array<{
@@ -13,16 +13,17 @@
13
13
  * });
14
14
  * ```
15
15
  */
16
- import * as readline from 'readline';
16
+ import * as readline from 'node:readline';
17
17
  import chalk from 'chalk';
18
+ import { isNonTTY } from './prompt-json.js';
18
19
  // ANSI escape codes for terminal control
19
- const ESC = '\x1b';
20
+ const ESC = '\u001B';
20
21
  const CSI = `${ESC}[`;
21
22
  // Control characters
22
- const CTRL_C = '\x03';
23
- const CTRL_D = '\x04';
24
- const BACKSPACE = '\x7f';
25
- const DELETE = '\x1b[3~';
23
+ const CTRL_C = '\u0003';
24
+ const CTRL_D = '\u0004';
25
+ const BACKSPACE = '\u007F';
26
+ const DELETE = '\u001B[3~';
26
27
  const ENTER = '\r';
27
28
  const NEWLINE = '\n';
28
29
  // Arrow keys (CSI sequences)
@@ -60,18 +61,6 @@ function moveDown(n) {
60
61
  function moveToColumn(col) {
61
62
  process.stdout.write(`${CSI}${col + 1}G`);
62
63
  }
63
- /**
64
- * Clear from cursor to end of screen
65
- */
66
- function clearToEnd() {
67
- process.stdout.write(`${CSI}J`);
68
- }
69
- /**
70
- * Hide cursor
71
- */
72
- function hideCursor() {
73
- process.stdout.write(`${CSI}?25l`);
74
- }
75
64
  /**
76
65
  * Show cursor
77
66
  */
@@ -95,7 +84,7 @@ function showCursor() {
95
84
  export async function multiLineInput(options) {
96
85
  const { message, default: defaultValue = '', hint = 'Ctrl+D to finish, Ctrl+C to cancel', required = false, validate, } = options;
97
86
  // If not a TTY, return the default value
98
- if (!process.stdin.isTTY) {
87
+ if (isNonTTY()) {
99
88
  return { value: defaultValue, cancelled: false };
100
89
  }
101
90
  return new Promise((resolve) => {
@@ -192,7 +192,6 @@ export declare abstract class PMOCommand extends PromptCommand {
192
192
  * @param code - Error code for JSON output (e.g., 'NOT_FOUND', 'DOCKER_NOT_RUNNING')
193
193
  * @param message - Human-readable error message (used in both modes)
194
194
  * @param options - Configuration for error handling
195
- * @returns never - always throws or exits
196
195
  *
197
196
  * @example
198
197
  * ```typescript
@@ -3,7 +3,7 @@ import inquirer from 'inquirer';
3
3
  import { getPMOContext } from './pmo-context.js';
4
4
  import { styles } from '../styles.js';
5
5
  import { PromptCommand } from '../prompt-command.js';
6
- import { shouldOutputJson, outputPromptAsJson, outputErrorAsJson, createMetadata, } from '../prompt-json.js';
6
+ import { shouldOutputJson, isNonTTY, outputPromptAsJson, outputErrorAsJson, createMetadata, } from '../prompt-json.js';
7
7
  /**
8
8
  * Base flags for JSON/agent mode support
9
9
  * Include these in your command's flags by spreading: ...jsonModeFlags
@@ -168,7 +168,7 @@ export class PMOCommand extends PromptCommand {
168
168
  return numA - numB;
169
169
  });
170
170
  // Auto-detect non-TTY: switch to JSON mode when no TTY present
171
- const effectiveJsonMode = options?.jsonMode ?? (!process.stdin.isTTY
171
+ const effectiveJsonMode = options?.jsonMode ?? (isNonTTY()
172
172
  ? {
173
173
  flags: { json: true },
174
174
  commandName: this.id ?? 'unknown',
@@ -234,7 +234,7 @@ export class PMOCommand extends PromptCommand {
234
234
  async selectFromList(options) {
235
235
  const { message, items, getName, getValue, getCommand, jsonMode, allowCancel = false, cancelValue, } = options;
236
236
  // Auto-detect non-TTY: switch to JSON mode when no TTY present
237
- const effectiveJsonMode = jsonMode ?? (!process.stdin.isTTY
237
+ const effectiveJsonMode = jsonMode ?? (isNonTTY()
238
238
  ? { flags: { json: true }, commandName: this.id ?? 'unknown' }
239
239
  : null);
240
240
  // Build choices with command field
@@ -285,7 +285,7 @@ export class PMOCommand extends PromptCommand {
285
285
  async promptForInput(options) {
286
286
  const { message, fieldName, defaultValue, validate, jsonMode } = options;
287
287
  // Auto-detect non-TTY: switch to JSON mode when no TTY present
288
- const effectiveJsonMode = jsonMode ?? (!process.stdin.isTTY
288
+ const effectiveJsonMode = jsonMode ?? (isNonTTY()
289
289
  ? { flags: { json: true }, commandName: this.id ?? 'unknown', commandHint: '', example: undefined }
290
290
  : null);
291
291
  // Check for JSON mode
@@ -323,7 +323,6 @@ export class PMOCommand extends PromptCommand {
323
323
  * @param code - Error code for JSON output (e.g., 'NOT_FOUND', 'DOCKER_NOT_RUNNING')
324
324
  * @param message - Human-readable error message (used in both modes)
325
325
  * @param options - Configuration for error handling
326
- * @returns never - always throws or exits
327
326
  *
328
327
  * @example
329
328
  * ```typescript
@@ -35,6 +35,9 @@ export declare const PMO_TABLES: {
35
35
  readonly ticket_templates: "pmo_ticket_templates";
36
36
  readonly roadmaps: "pmo_roadmaps";
37
37
  readonly roadmap_projects: "pmo_roadmap_projects";
38
+ readonly label_groups: "pmo_label_groups";
39
+ readonly labels: "pmo_labels";
40
+ readonly ticket_labels: "pmo_ticket_labels";
38
41
  readonly columns: "pmo_columns";
39
42
  readonly board_tickets: "pmo_board_tickets";
40
43
  readonly statuses: "pmo_statuses";
@@ -71,6 +74,9 @@ export declare const PMO_TABLE_SCHEMAS: {
71
74
  readonly phase_templates: "\n CREATE TABLE IF NOT EXISTS pmo_phase_templates (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n description TEXT,\n is_builtin INTEGER NOT NULL DEFAULT 0,\n phases TEXT NOT NULL,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n )";
72
75
  readonly actions: "\n CREATE TABLE IF NOT EXISTS pmo_actions (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n description TEXT,\n prompt TEXT NOT NULL,\n end_prompt TEXT,\n suggested_for_categories TEXT,\n default_move_to_category TEXT,\n modifies_code INTEGER NOT NULL DEFAULT 1,\n is_builtin INTEGER NOT NULL DEFAULT 0,\n position INTEGER NOT NULL DEFAULT 0,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n )";
73
76
  readonly ticket_templates: "\n CREATE TABLE IF NOT EXISTS pmo_ticket_templates (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n description TEXT,\n is_builtin INTEGER NOT NULL DEFAULT 0,\n title_pattern TEXT,\n description_template TEXT,\n default_priority TEXT,\n default_category TEXT,\n default_status_id TEXT,\n default_assignee TEXT,\n default_owner TEXT,\n default_labels TEXT NOT NULL DEFAULT '[]',\n suggested_subtasks TEXT NOT NULL DEFAULT '[]',\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n )";
77
+ readonly label_groups: "\n CREATE TABLE IF NOT EXISTS pmo_label_groups (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n description TEXT,\n is_exclusive INTEGER NOT NULL DEFAULT 1,\n is_required INTEGER NOT NULL DEFAULT 0,\n position INTEGER NOT NULL DEFAULT 0,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n )";
78
+ readonly labels: "\n CREATE TABLE IF NOT EXISTS pmo_labels (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n color TEXT,\n description TEXT,\n group_id TEXT REFERENCES pmo_label_groups(id) ON DELETE SET NULL,\n position INTEGER NOT NULL DEFAULT 0,\n is_builtin INTEGER NOT NULL DEFAULT 0,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n UNIQUE(name, group_id)\n )";
79
+ readonly ticket_labels: "\n CREATE TABLE IF NOT EXISTS pmo_ticket_labels (\n ticket_id TEXT NOT NULL REFERENCES pmo_tickets(id) ON DELETE CASCADE,\n label_id TEXT NOT NULL REFERENCES pmo_labels(id) ON DELETE CASCADE,\n PRIMARY KEY (ticket_id, label_id)\n )";
74
80
  readonly roadmaps: "\n CREATE TABLE IF NOT EXISTS pmo_roadmaps (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n description TEXT,\n is_default INTEGER NOT NULL DEFAULT 0,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n )";
75
81
  readonly roadmap_projects: "\n CREATE TABLE IF NOT EXISTS pmo_roadmap_projects (\n roadmap_id TEXT NOT NULL REFERENCES pmo_roadmaps(id) ON DELETE CASCADE,\n project_id TEXT NOT NULL REFERENCES pmo_projects(id) ON DELETE CASCADE,\n position INTEGER NOT NULL DEFAULT 0,\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (roadmap_id, project_id)\n )";
76
82
  };
@@ -41,6 +41,10 @@ export const PMO_TABLES = {
41
41
  // Roadmap tables (ordered collections of projects for documentation)
42
42
  roadmaps: 'pmo_roadmaps', // Named roadmap definitions
43
43
  roadmap_projects: 'pmo_roadmap_projects', // Many-to-many: roadmaps ↔ projects with ordering
44
+ // Label system tables
45
+ label_groups: 'pmo_label_groups',
46
+ labels: 'pmo_labels',
47
+ ticket_labels: 'pmo_ticket_labels',
44
48
  // Legacy tables (deprecated, kept for migration)
45
49
  columns: 'pmo_columns', // DEPRECATED: use workflow_statuses
46
50
  board_tickets: 'pmo_board_tickets', // DEPRECATED: tickets now use status_id directly
@@ -427,6 +431,37 @@ export const PMO_TABLE_SCHEMAS = {
427
431
  suggested_subtasks TEXT NOT NULL DEFAULT '[]',
428
432
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
429
433
  )`,
434
+ // Label groups (workspace-scoped grouping for labels)
435
+ label_groups: `
436
+ CREATE TABLE IF NOT EXISTS ${PMO_TABLES.label_groups} (
437
+ id TEXT PRIMARY KEY,
438
+ name TEXT NOT NULL UNIQUE,
439
+ description TEXT,
440
+ is_exclusive INTEGER NOT NULL DEFAULT 1,
441
+ is_required INTEGER NOT NULL DEFAULT 0,
442
+ position INTEGER NOT NULL DEFAULT 0,
443
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
444
+ )`,
445
+ // Labels (workspace-scoped, optionally grouped)
446
+ labels: `
447
+ CREATE TABLE IF NOT EXISTS ${PMO_TABLES.labels} (
448
+ id TEXT PRIMARY KEY,
449
+ name TEXT NOT NULL,
450
+ color TEXT,
451
+ description TEXT,
452
+ group_id TEXT REFERENCES ${PMO_TABLES.label_groups}(id) ON DELETE SET NULL,
453
+ position INTEGER NOT NULL DEFAULT 0,
454
+ is_builtin INTEGER NOT NULL DEFAULT 0,
455
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
456
+ UNIQUE(name, group_id)
457
+ )`,
458
+ // Junction table: ticket-label associations (replaces JSON labels field)
459
+ ticket_labels: `
460
+ CREATE TABLE IF NOT EXISTS ${PMO_TABLES.ticket_labels} (
461
+ ticket_id TEXT NOT NULL REFERENCES ${PMO_TABLES.tickets}(id) ON DELETE CASCADE,
462
+ label_id TEXT NOT NULL REFERENCES ${PMO_TABLES.labels}(id) ON DELETE CASCADE,
463
+ PRIMARY KEY (ticket_id, label_id)
464
+ )`,
430
465
  // Roadmap definitions (named collections of projects for documentation)
431
466
  roadmaps: `
432
467
  CREATE TABLE IF NOT EXISTS ${PMO_TABLES.roadmaps} (
@@ -502,6 +537,12 @@ export const PMO_INDEXES = `
502
537
  CREATE INDEX IF NOT EXISTS idx_pmo_roadmap_projects_position ON ${PMO_TABLES.roadmap_projects}(roadmap_id, position);
503
538
  CREATE INDEX IF NOT EXISTS idx_pmo_categories_type ON ${PMO_TABLES.categories}(type);
504
539
  CREATE INDEX IF NOT EXISTS idx_pmo_categories_position ON ${PMO_TABLES.categories}(type, position);
540
+ CREATE INDEX IF NOT EXISTS idx_pmo_label_groups_position ON ${PMO_TABLES.label_groups}(position);
541
+ CREATE INDEX IF NOT EXISTS idx_pmo_labels_group ON ${PMO_TABLES.labels}(group_id);
542
+ CREATE INDEX IF NOT EXISTS idx_pmo_labels_position ON ${PMO_TABLES.labels}(group_id, position);
543
+ CREATE INDEX IF NOT EXISTS idx_pmo_labels_builtin ON ${PMO_TABLES.labels}(is_builtin);
544
+ CREATE INDEX IF NOT EXISTS idx_pmo_ticket_labels_ticket ON ${PMO_TABLES.ticket_labels}(ticket_id);
545
+ CREATE INDEX IF NOT EXISTS idx_pmo_ticket_labels_label ON ${PMO_TABLES.ticket_labels}(label_id);
505
546
  `;
506
547
  // =============================================================================
507
548
  // Combined Schema
@@ -540,6 +581,9 @@ export const PMO_SCHEMA_SQL = [
540
581
  PMO_TABLE_SCHEMAS.id_sequences, // Sequence counters for ID generation
541
582
  PMO_TABLE_SCHEMAS.actions, // Work actions (reusable agent prompts)
542
583
  PMO_TABLE_SCHEMAS.ticket_templates, // Ticket templates for quick creation
584
+ PMO_TABLE_SCHEMAS.label_groups, // Label groups (before labels for FK)
585
+ PMO_TABLE_SCHEMAS.labels, // Labels (before ticket_labels for FK)
586
+ PMO_TABLE_SCHEMAS.ticket_labels, // Ticket-label junction table
543
587
  PMO_TABLE_SCHEMAS.roadmaps, // Named roadmap definitions
544
588
  PMO_TABLE_SCHEMAS.roadmap_projects, // Roadmap-to-project associations
545
589
  // Legacy tables (kept for migration, will be dropped after data migrated)
@@ -169,7 +169,7 @@ export class ActionStorage {
169
169
  ? JSON.parse(row.default_category)
170
170
  : undefined,
171
171
  defaultMoveToCategory: row.default_category,
172
- modifiesCode: row.is_builtin === 1,
172
+ modifiesCode: row.modifies_code === 1,
173
173
  isBuiltin: row.is_builtin === 1,
174
174
  createdAt: new Date(row.created_at),
175
175
  };
@@ -42,6 +42,12 @@ export declare function seedBuiltinCategories(db: Database.Database): void;
42
42
  * Preserves any existing user-defined priority scale.
43
43
  */
44
44
  export declare function seedDefaultPriorities(db: Database.Database): void;
45
+ /**
46
+ * Seed built-in label groups and labels.
47
+ * Creates Function, Type, and Area groups with their labels.
48
+ * Migrates existing ticket category values to Function labels.
49
+ */
50
+ export declare function seedBuiltinLabels(db: Database.Database): void;
45
51
  /**
46
52
  * Update board timestamp for a project.
47
53
  */