@proletariat/cli 0.3.36 → 0.3.41
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/README.md +37 -2
- package/bin/dev.js +0 -0
- package/dist/commands/branch/where.js +6 -17
- package/dist/commands/epic/ticket.js +7 -24
- package/dist/commands/execution/config.js +4 -14
- package/dist/commands/execution/logs.js +6 -0
- package/dist/commands/execution/view.js +8 -0
- package/dist/commands/init.js +4 -8
- package/dist/commands/mcp-server.js +2 -1
- package/dist/commands/pmo/init.js +12 -40
- package/dist/commands/qa/index.d.ts +54 -0
- package/dist/commands/qa/index.js +762 -0
- package/dist/commands/repo/view.js +2 -8
- package/dist/commands/session/attach.js +4 -4
- package/dist/commands/session/health.js +4 -4
- package/dist/commands/session/list.js +1 -19
- package/dist/commands/session/peek.js +6 -6
- package/dist/commands/session/poke.js +2 -2
- package/dist/commands/ticket/epic.js +17 -43
- package/dist/commands/work/spawn-all.js +1 -1
- package/dist/commands/work/spawn.js +15 -4
- package/dist/commands/work/start.js +17 -9
- package/dist/commands/work/watch.js +1 -1
- package/dist/commands/workspace/prune.js +3 -3
- package/dist/hooks/init.js +21 -10
- package/dist/lib/agents/commands.d.ts +5 -0
- package/dist/lib/agents/commands.js +143 -97
- package/dist/lib/database/drizzle-schema.d.ts +465 -0
- package/dist/lib/database/drizzle-schema.js +53 -0
- package/dist/lib/database/index.d.ts +47 -1
- package/dist/lib/database/index.js +138 -20
- package/dist/lib/execution/runners.d.ts +34 -0
- package/dist/lib/execution/runners.js +134 -7
- package/dist/lib/execution/session-utils.d.ts +5 -0
- package/dist/lib/execution/session-utils.js +45 -3
- package/dist/lib/execution/spawner.js +15 -2
- package/dist/lib/execution/storage.d.ts +1 -1
- package/dist/lib/execution/storage.js +17 -2
- package/dist/lib/execution/types.d.ts +1 -0
- package/dist/lib/mcp/tools/index.d.ts +1 -0
- package/dist/lib/mcp/tools/index.js +1 -0
- package/dist/lib/mcp/tools/tmux.d.ts +16 -0
- package/dist/lib/mcp/tools/tmux.js +182 -0
- package/dist/lib/mcp/tools/work.js +52 -0
- package/dist/lib/pmo/schema.d.ts +1 -1
- package/dist/lib/pmo/schema.js +1 -0
- package/dist/lib/pmo/storage/base.js +207 -0
- package/dist/lib/pmo/storage/dependencies.d.ts +1 -0
- package/dist/lib/pmo/storage/dependencies.js +11 -3
- package/dist/lib/pmo/storage/epics.js +1 -1
- package/dist/lib/pmo/storage/helpers.d.ts +4 -4
- package/dist/lib/pmo/storage/helpers.js +36 -26
- package/dist/lib/pmo/storage/projects.d.ts +2 -0
- package/dist/lib/pmo/storage/projects.js +207 -119
- package/dist/lib/pmo/storage/specs.d.ts +2 -0
- package/dist/lib/pmo/storage/specs.js +274 -188
- package/dist/lib/pmo/storage/tickets.d.ts +2 -0
- package/dist/lib/pmo/storage/tickets.js +350 -290
- package/dist/lib/pmo/storage/views.d.ts +2 -0
- package/dist/lib/pmo/storage/views.js +183 -130
- package/dist/lib/prompt-json.d.ts +5 -0
- package/dist/lib/prompt-json.js +9 -0
- package/oclif.manifest.json +3293 -3190
- package/package.json +11 -6
- package/LICENSE +0 -190
|
@@ -2,7 +2,7 @@ import { Args } from '@oclif/core';
|
|
|
2
2
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
3
3
|
import { colors, format } from '../../lib/colors.js';
|
|
4
4
|
import { findHQRoot, getWorkspaceRepoInfo, } from '../../lib/repos/index.js';
|
|
5
|
-
import {
|
|
5
|
+
import { getWorktreesForRepo } from '../../lib/database/index.js';
|
|
6
6
|
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
7
7
|
import { visualPadEnd } from '../../lib/string-utils.js';
|
|
8
8
|
export default class View extends PMOCommand {
|
|
@@ -102,13 +102,7 @@ export default class View extends PMOCommand {
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
// Get agent worktree info
|
|
105
|
-
const
|
|
106
|
-
const worktrees = db.prepare(`
|
|
107
|
-
SELECT agent_name, is_clean, commits_ahead, branch
|
|
108
|
-
FROM agent_worktrees
|
|
109
|
-
WHERE repo_name = ?
|
|
110
|
-
`).all(repoName);
|
|
111
|
-
db.close();
|
|
105
|
+
const worktrees = getWorktreesForRepo(hqPath, repoName);
|
|
112
106
|
if (worktrees.length > 0) {
|
|
113
107
|
this.log(format.subtitle('\n👥 Agent Worktrees:'));
|
|
114
108
|
for (const wt of worktrees) {
|
|
@@ -7,7 +7,7 @@ import Database from 'better-sqlite3';
|
|
|
7
7
|
import { styles } from '../../lib/styles.js';
|
|
8
8
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
9
9
|
import { ExecutionStorage } from '../../lib/execution/index.js';
|
|
10
|
-
import { parseSessionName, getHostTmuxSessionNames, getContainerTmuxSessionMap, flattenContainerSessions, findSessionForExecution, } from '../../lib/execution/session-utils.js';
|
|
10
|
+
import { parseSessionName, getHostTmuxSessionNames, getContainerTmuxSessionMap, flattenContainerSessions, findContainerSessionsByPrefix, findSessionForExecution, } from '../../lib/execution/session-utils.js';
|
|
11
11
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
12
12
|
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
13
13
|
export default class SessionAttach extends PMOCommand {
|
|
@@ -154,7 +154,7 @@ export default class SessionAttach extends PMOCommand {
|
|
|
154
154
|
// If sessionId is NULL, try to find session by naming convention
|
|
155
155
|
if (!exec.sessionId) {
|
|
156
156
|
if (isContainer && exec.containerId) {
|
|
157
|
-
const containerSessions = containerTmuxSessions
|
|
157
|
+
const containerSessions = findContainerSessionsByPrefix(containerTmuxSessions, exec.containerId);
|
|
158
158
|
const match = findSessionForExecution(exec.ticketId, exec.agentName, containerSessions);
|
|
159
159
|
if (match) {
|
|
160
160
|
actualSessionId = match;
|
|
@@ -177,8 +177,8 @@ export default class SessionAttach extends PMOCommand {
|
|
|
177
177
|
else {
|
|
178
178
|
// sessionId is set, verify it exists
|
|
179
179
|
if (isContainer && exec.containerId) {
|
|
180
|
-
const containerSessions = containerTmuxSessions
|
|
181
|
-
exists = containerSessions
|
|
180
|
+
const containerSessions = findContainerSessionsByPrefix(containerTmuxSessions, exec.containerId);
|
|
181
|
+
exists = containerSessions.includes(exec.sessionId);
|
|
182
182
|
containerId = exec.containerId;
|
|
183
183
|
}
|
|
184
184
|
else {
|
|
@@ -5,7 +5,7 @@ import Database from 'better-sqlite3';
|
|
|
5
5
|
import { styles } from '../../lib/styles.js';
|
|
6
6
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
7
7
|
import { ExecutionStorage } from '../../lib/execution/index.js';
|
|
8
|
-
import { parseSessionName, getHostTmuxSessionNames, getContainerTmuxSessionMap, flattenContainerSessions, findSessionForExecution, captureTmuxPane, } from '../../lib/execution/session-utils.js';
|
|
8
|
+
import { parseSessionName, getHostTmuxSessionNames, getContainerTmuxSessionMap, flattenContainerSessions, findContainerSessionsByPrefix, findSessionForExecution, captureTmuxPane, } from '../../lib/execution/session-utils.js';
|
|
9
9
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
10
10
|
import { visualPadEnd } from '../../lib/string-utils.js';
|
|
11
11
|
import { shouldOutputJson, outputSuccessAsJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
@@ -168,7 +168,7 @@ export default class SessionHealth extends PMOCommand {
|
|
|
168
168
|
// Try to find session if sessionId is NULL
|
|
169
169
|
if (!exec.sessionId) {
|
|
170
170
|
if (isContainer && exec.containerId) {
|
|
171
|
-
const containerSessions = containerTmuxSessions
|
|
171
|
+
const containerSessions = findContainerSessionsByPrefix(containerTmuxSessions, exec.containerId);
|
|
172
172
|
const match = findSessionForExecution(exec.ticketId, exec.agentName, containerSessions);
|
|
173
173
|
if (match) {
|
|
174
174
|
actualSessionId = match;
|
|
@@ -188,8 +188,8 @@ export default class SessionHealth extends PMOCommand {
|
|
|
188
188
|
}
|
|
189
189
|
else {
|
|
190
190
|
if (isContainer && exec.containerId) {
|
|
191
|
-
const containerSessions = containerTmuxSessions
|
|
192
|
-
exists = containerSessions
|
|
191
|
+
const containerSessions = findContainerSessionsByPrefix(containerTmuxSessions, exec.containerId);
|
|
192
|
+
exists = containerSessions.includes(exec.sessionId);
|
|
193
193
|
containerId = exec.containerId;
|
|
194
194
|
}
|
|
195
195
|
else {
|
|
@@ -4,28 +4,10 @@ import Database from 'better-sqlite3';
|
|
|
4
4
|
import { styles } from '../../lib/styles.js';
|
|
5
5
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
6
6
|
import { ExecutionStorage } from '../../lib/execution/index.js';
|
|
7
|
-
import { parseSessionName, getHostTmuxSessionNames, getContainerTmuxSessionMap, flattenContainerSessions, findSessionForExecution, } from '../../lib/execution/session-utils.js';
|
|
7
|
+
import { parseSessionName, getHostTmuxSessionNames, getContainerTmuxSessionMap, flattenContainerSessions, findContainerSessionsByPrefix, findSessionForExecution, } from '../../lib/execution/session-utils.js';
|
|
8
8
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
9
9
|
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
10
10
|
import { visualPadEnd } from '../../lib/string-utils.js';
|
|
11
|
-
/**
|
|
12
|
-
* Find container sessions using prefix matching.
|
|
13
|
-
* Handles cases where the stored containerId format differs from docker ps output
|
|
14
|
-
* (e.g., full 64-char ID vs 12-char short ID).
|
|
15
|
-
*/
|
|
16
|
-
function findContainerSessionsByPrefix(containerTmuxSessions, containerId) {
|
|
17
|
-
// Try exact match first
|
|
18
|
-
const exact = containerTmuxSessions.get(containerId);
|
|
19
|
-
if (exact)
|
|
20
|
-
return exact;
|
|
21
|
-
// Fall back to prefix matching (handles short vs full ID mismatches)
|
|
22
|
-
for (const [key, sessions] of containerTmuxSessions) {
|
|
23
|
-
if (key.startsWith(containerId) || containerId.startsWith(key)) {
|
|
24
|
-
return sessions;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return [];
|
|
28
|
-
}
|
|
29
11
|
export default class SessionList extends PMOCommand {
|
|
30
12
|
static description = 'List active tmux sessions (host and container)';
|
|
31
13
|
static examples = [
|
|
@@ -4,7 +4,7 @@ import Database from 'better-sqlite3';
|
|
|
4
4
|
import { styles } from '../../lib/styles.js';
|
|
5
5
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
6
6
|
import { ExecutionStorage } from '../../lib/execution/index.js';
|
|
7
|
-
import { parseSessionName, getHostTmuxSessionNames, getContainerTmuxSessionMap, flattenContainerSessions, findSessionForExecution, captureTmuxPane, } from '../../lib/execution/session-utils.js';
|
|
7
|
+
import { parseSessionName, getHostTmuxSessionNames, getContainerTmuxSessionMap, flattenContainerSessions, findContainerSessionsByPrefix, findSessionForExecution, captureTmuxPane, } from '../../lib/execution/session-utils.js';
|
|
8
8
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
9
9
|
import { shouldOutputJson, outputSuccessAsJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
10
10
|
// =============================================================================
|
|
@@ -41,7 +41,7 @@ export default class SessionPeek extends PMOCommand {
|
|
|
41
41
|
const { args, flags } = await this.parse(SessionPeek);
|
|
42
42
|
const jsonMode = shouldOutputJson(flags);
|
|
43
43
|
// Discover all verified sessions
|
|
44
|
-
const sessions = this.getVerifiedSessions(
|
|
44
|
+
const sessions = this.getVerifiedSessions();
|
|
45
45
|
if (sessions.length === 0) {
|
|
46
46
|
if (jsonMode) {
|
|
47
47
|
outputErrorAsJson('NO_SESSIONS', 'No active sessions found.', createMetadata('session peek', flags));
|
|
@@ -201,7 +201,7 @@ export default class SessionPeek extends PMOCommand {
|
|
|
201
201
|
* Get verified sessions from DB that have actual tmux processes.
|
|
202
202
|
* Same discovery pattern as attach.ts and list.ts.
|
|
203
203
|
*/
|
|
204
|
-
getVerifiedSessions(
|
|
204
|
+
getVerifiedSessions() {
|
|
205
205
|
const sessions = [];
|
|
206
206
|
let executionStorage = null;
|
|
207
207
|
let db = null;
|
|
@@ -232,7 +232,7 @@ export default class SessionPeek extends PMOCommand {
|
|
|
232
232
|
let actualSessionId = exec.sessionId;
|
|
233
233
|
if (!exec.sessionId) {
|
|
234
234
|
if (isContainer && exec.containerId) {
|
|
235
|
-
const containerSessions = containerTmuxSessions
|
|
235
|
+
const containerSessions = findContainerSessionsByPrefix(containerTmuxSessions, exec.containerId);
|
|
236
236
|
const match = findSessionForExecution(exec.ticketId, exec.agentName, containerSessions);
|
|
237
237
|
if (match) {
|
|
238
238
|
actualSessionId = match;
|
|
@@ -252,8 +252,8 @@ export default class SessionPeek extends PMOCommand {
|
|
|
252
252
|
}
|
|
253
253
|
else {
|
|
254
254
|
if (isContainer && exec.containerId) {
|
|
255
|
-
const containerSessions = containerTmuxSessions
|
|
256
|
-
exists = containerSessions
|
|
255
|
+
const containerSessions = findContainerSessionsByPrefix(containerTmuxSessions, exec.containerId);
|
|
256
|
+
exists = containerSessions.includes(exec.sessionId);
|
|
257
257
|
containerId = exec.containerId;
|
|
258
258
|
}
|
|
259
259
|
else {
|
|
@@ -5,7 +5,7 @@ import Database from 'better-sqlite3';
|
|
|
5
5
|
import { styles } from '../../lib/styles.js';
|
|
6
6
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
7
7
|
import { ExecutionStorage } from '../../lib/execution/index.js';
|
|
8
|
-
import { getHostTmuxSessionNames, getContainerTmuxSessionMap, findSessionForExecution, } from '../../lib/execution/session-utils.js';
|
|
8
|
+
import { getHostTmuxSessionNames, getContainerTmuxSessionMap, findContainerSessionsByPrefix, findSessionForExecution, } from '../../lib/execution/session-utils.js';
|
|
9
9
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
10
10
|
import { shouldOutputJson, outputSuccessAsJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
11
11
|
// =============================================================================
|
|
@@ -183,7 +183,7 @@ export default class SessionPoke extends PMOCommand {
|
|
|
183
183
|
if (!exec.sessionId) {
|
|
184
184
|
if (isContainer && exec.containerId) {
|
|
185
185
|
const containerTmuxSessions = getContainerTmuxSessionMap();
|
|
186
|
-
const containerSessions = containerTmuxSessions
|
|
186
|
+
const containerSessions = findContainerSessionsByPrefix(containerTmuxSessions, exec.containerId);
|
|
187
187
|
const match = findSessionForExecution(exec.ticketId, exec.agentName, containerSessions);
|
|
188
188
|
if (match) {
|
|
189
189
|
actualSessionId = match;
|
|
@@ -68,11 +68,10 @@ export default class TicketEpic extends PMOCommand {
|
|
|
68
68
|
const projectId = await this.requireProject();
|
|
69
69
|
// Get all epics
|
|
70
70
|
const epics = await this.storage.listEpics(projectId);
|
|
71
|
-
//
|
|
72
|
-
const db = this.storage.db;
|
|
71
|
+
// Helper to get ticket's epic ID via storage layer
|
|
73
72
|
const getTicketEpicId = (ticketId) => {
|
|
74
|
-
const
|
|
75
|
-
return
|
|
73
|
+
const ticket = allTickets.find((t) => t.id === ticketId);
|
|
74
|
+
return ticket?.epicId ?? null;
|
|
76
75
|
};
|
|
77
76
|
let ticketId = args.id;
|
|
78
77
|
let epicId = args['epic-id'];
|
|
@@ -119,11 +118,7 @@ export default class TicketEpic extends PMOCommand {
|
|
|
119
118
|
}
|
|
120
119
|
const currentEpic = epics.find(e => e.id === currentEpicId);
|
|
121
120
|
// Update the ticket
|
|
122
|
-
|
|
123
|
-
UPDATE pmo_tickets
|
|
124
|
-
SET epic_id = NULL, updated_at = ?
|
|
125
|
-
WHERE id = ?
|
|
126
|
-
`).run(Date.now(), ticketId);
|
|
121
|
+
await this.storage.unlinkTicketFromEpic(ticketId);
|
|
127
122
|
await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
|
|
128
123
|
this.log(styles.success(`\n✅ Unlinked ${styles.emphasis(ticketId)} from ${currentEpic?.title || currentEpicId}`));
|
|
129
124
|
this.log(styles.muted(` Title: ${ticket.title}`));
|
|
@@ -156,11 +151,7 @@ export default class TicketEpic extends PMOCommand {
|
|
|
156
151
|
}
|
|
157
152
|
if (selected === '__none__') {
|
|
158
153
|
// Unlink
|
|
159
|
-
|
|
160
|
-
UPDATE pmo_tickets
|
|
161
|
-
SET epic_id = NULL, updated_at = ?
|
|
162
|
-
WHERE id = ?
|
|
163
|
-
`).run(Date.now(), ticketId);
|
|
154
|
+
await this.storage.unlinkTicketFromEpic(ticketId);
|
|
164
155
|
const currentEpic = epics.find(e => e.id === currentEpicId);
|
|
165
156
|
await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
|
|
166
157
|
this.log(styles.success(`\n✅ Unlinked ${styles.emphasis(ticketId)} from ${currentEpic?.title || currentEpicId}`));
|
|
@@ -180,11 +171,7 @@ export default class TicketEpic extends PMOCommand {
|
|
|
180
171
|
return;
|
|
181
172
|
}
|
|
182
173
|
// Update the ticket
|
|
183
|
-
|
|
184
|
-
UPDATE pmo_tickets
|
|
185
|
-
SET epic_id = ?, updated_at = ?
|
|
186
|
-
WHERE id = ?
|
|
187
|
-
`).run(epicId, Date.now(), ticketId);
|
|
174
|
+
await this.storage.linkTicketToEpic(ticketId, epicId);
|
|
188
175
|
await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
|
|
189
176
|
this.log(styles.success(`\n✅ Linked ${styles.emphasis(ticketId)} to ${styles.emphasis(epicId)}`));
|
|
190
177
|
this.log(styles.muted(` Title: ${ticket.title}`));
|
|
@@ -202,35 +189,21 @@ export default class TicketEpic extends PMOCommand {
|
|
|
202
189
|
this.log(styles.warning('No tickets found.'));
|
|
203
190
|
return;
|
|
204
191
|
}
|
|
205
|
-
// Get epics
|
|
206
|
-
const
|
|
207
|
-
const epics = db.prepare(`
|
|
208
|
-
SELECT id, title, status FROM pmo_epics
|
|
209
|
-
WHERE project_id = ?
|
|
210
|
-
ORDER BY status, title
|
|
211
|
-
`).all(projectId);
|
|
192
|
+
// Get epics via storage layer
|
|
193
|
+
const epics = await this.storage.listEpics(projectId);
|
|
212
194
|
// Filter tickets if --from-epic specified
|
|
213
195
|
let filteredTickets = allTickets;
|
|
214
196
|
if (flags['from-epic']) {
|
|
215
|
-
|
|
216
|
-
const epicTickets = db.prepare(`
|
|
217
|
-
SELECT id FROM pmo_tickets
|
|
218
|
-
WHERE project_id = ? AND epic_id = ?
|
|
219
|
-
`).all(projectId, flags['from-epic']);
|
|
220
|
-
const epicTicketIds = new Set(epicTickets.map(t => t.id));
|
|
221
|
-
filteredTickets = allTickets.filter(t => epicTicketIds.has(t.id));
|
|
197
|
+
filteredTickets = allTickets.filter(t => t.epicId === flags['from-epic']);
|
|
222
198
|
}
|
|
223
199
|
if (filteredTickets.length === 0) {
|
|
224
200
|
this.log(styles.warning('No tickets found matching filter.'));
|
|
225
201
|
return;
|
|
226
202
|
}
|
|
227
|
-
// Get current epic for each ticket
|
|
203
|
+
// Get current epic for each ticket (already available on Ticket objects)
|
|
228
204
|
const ticketEpics = new Map();
|
|
229
205
|
for (const ticket of filteredTickets) {
|
|
230
|
-
|
|
231
|
-
SELECT epic_id FROM pmo_tickets WHERE id = ?
|
|
232
|
-
`).get(ticket.id);
|
|
233
|
-
ticketEpics.set(ticket.id, row?.epic_id || null);
|
|
206
|
+
ticketEpics.set(ticket.id, ticket.epicId ?? null);
|
|
234
207
|
}
|
|
235
208
|
// Select tickets to link
|
|
236
209
|
const { selectedTickets } = await this.prompt([{
|
|
@@ -302,11 +275,12 @@ export default class TicketEpic extends PMOCommand {
|
|
|
302
275
|
let failCount = 0;
|
|
303
276
|
for (const ticketId of selectedTickets) {
|
|
304
277
|
try {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
278
|
+
if (targetEpic) {
|
|
279
|
+
await this.storage.linkTicketToEpic(ticketId, targetEpic);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
await this.storage.unlinkTicketFromEpic(ticketId);
|
|
283
|
+
}
|
|
310
284
|
const action = targetEpic ? `Linked to ${targetEpic}` : 'Removed epic link';
|
|
311
285
|
this.log(styles.success(`${ticketId}: ${action}`));
|
|
312
286
|
successCount++;
|
|
@@ -24,7 +24,7 @@ export default class WorkSpawnAll extends PMOCommand {
|
|
|
24
24
|
default: false,
|
|
25
25
|
}),
|
|
26
26
|
'create-pr': Flags.boolean({
|
|
27
|
-
description: 'Create PR when work is ready',
|
|
27
|
+
description: 'Create PR when work is ready (canonical flag for PR behavior)',
|
|
28
28
|
default: false,
|
|
29
29
|
}),
|
|
30
30
|
executor: Flags.string({
|
|
@@ -25,6 +25,7 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
25
25
|
'<%= config.bin %> <%= command.id %> --count 5 --category ship --action implement # Filtered by category',
|
|
26
26
|
'<%= config.bin %> <%= command.id %> --count 5 --priority P0 --action implement # Filtered by priority',
|
|
27
27
|
'<%= config.bin %> <%= command.id %> --count 10 --diet --category ship,grow --action groom # Combined',
|
|
28
|
+
'<%= config.bin %> <%= command.id %> TKT-001 TKT-002 --create-pr # Create PR when work is ready',
|
|
28
29
|
];
|
|
29
30
|
static flags = {
|
|
30
31
|
...pmoBaseFlags,
|
|
@@ -93,11 +94,11 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
93
94
|
default: false,
|
|
94
95
|
}),
|
|
95
96
|
'create-pr': Flags.boolean({
|
|
96
|
-
description: 'Create PR when work is ready (batch mode only)',
|
|
97
|
+
description: 'Create PR when work is ready (canonical flag for PR behavior, batch mode only)',
|
|
97
98
|
default: false,
|
|
98
99
|
}),
|
|
99
100
|
'no-pr': Flags.boolean({
|
|
100
|
-
description: '
|
|
101
|
+
description: '[deprecated: use --create-pr instead] Skip PR creation (batch mode only)',
|
|
101
102
|
default: false,
|
|
102
103
|
}),
|
|
103
104
|
action: Flags.string({
|
|
@@ -153,6 +154,10 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
153
154
|
}
|
|
154
155
|
this.error(message);
|
|
155
156
|
};
|
|
157
|
+
// Deprecation guidance for --no-pr
|
|
158
|
+
if (flags['no-pr']) {
|
|
159
|
+
this.warn('--no-pr is deprecated. Omit --create-pr instead (PR creation is off by default). --no-pr will continue to work.');
|
|
160
|
+
}
|
|
156
161
|
// Parse ticket IDs from args (everything after flags)
|
|
157
162
|
const ticketIdArgs = argv;
|
|
158
163
|
// Try to infer project from ticket IDs if provided
|
|
@@ -294,6 +299,7 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
294
299
|
else if (allFlagsProvided && !flags.yes) {
|
|
295
300
|
// All flags provided but no --yes: return confirmation_needed with plan
|
|
296
301
|
const metadata = createMetadata('work spawn', flags);
|
|
302
|
+
metadata.resolvedPRMode = flags['create-pr'] ? 'create-pr' : 'no-pr';
|
|
297
303
|
// Build the confirm command with --yes
|
|
298
304
|
const ticketIds = ticketsToSpawn.map(t => t.id).join(' ');
|
|
299
305
|
let confirmCmd = `prlt work spawn ${ticketIds}`;
|
|
@@ -594,6 +600,7 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
594
600
|
// In JSON mode without --yes, return confirmation_needed
|
|
595
601
|
if (jsonMode && !flags.yes) {
|
|
596
602
|
const metadata = createMetadata('work spawn', flags);
|
|
603
|
+
metadata.resolvedPRMode = flags['create-pr'] ? 'create-pr' : 'no-pr';
|
|
597
604
|
const ticketIds = ticketsToSpawn.map(t => t.id).join(' ');
|
|
598
605
|
let confirmCmd = `prlt work spawn ${ticketIds}`;
|
|
599
606
|
if (flags.action)
|
|
@@ -1394,12 +1401,16 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
1394
1401
|
db.close();
|
|
1395
1402
|
// Output results
|
|
1396
1403
|
if (jsonMode) {
|
|
1397
|
-
// Output JSON execution results
|
|
1398
|
-
|
|
1404
|
+
// Output JSON execution results with resolved PR mode
|
|
1405
|
+
const spawnMetadata = createMetadata('work spawn', flags);
|
|
1406
|
+
spawnMetadata.resolvedPRMode = flags['create-pr'] ? 'create-pr' : 'no-pr';
|
|
1407
|
+
outputExecutionResultAsJson(executionResults, successCount, failCount, spawnMetadata);
|
|
1399
1408
|
}
|
|
1400
1409
|
else {
|
|
1410
|
+
const resolvedPRMode = flags['create-pr'] ? 'create-pr' : 'no-pr';
|
|
1401
1411
|
this.log('');
|
|
1402
1412
|
this.log(styles.success(`✓ Spawn results: ${successCount} started, ${failCount} failed`));
|
|
1413
|
+
this.log(styles.muted(` PR mode: ${resolvedPRMode}`));
|
|
1403
1414
|
}
|
|
1404
1415
|
}
|
|
1405
1416
|
catch (error) {
|
|
@@ -71,6 +71,7 @@ export default class WorkStart extends PMOCommand {
|
|
|
71
71
|
static description = 'Start work on a ticket (launches an agent to implement it)';
|
|
72
72
|
static examples = [
|
|
73
73
|
'<%= config.bin %> <%= command.id %> TKT-001',
|
|
74
|
+
'<%= config.bin %> <%= command.id %> TKT-001 --create-pr # Create PR when work is ready',
|
|
74
75
|
'<%= config.bin %> <%= command.id %> TKT-001 --mode foreground',
|
|
75
76
|
'<%= config.bin %> <%= command.id %> TKT-001 --mode tmux',
|
|
76
77
|
'<%= config.bin %> <%= command.id %> TKT-001 --mode terminal',
|
|
@@ -137,11 +138,11 @@ export default class WorkStart extends PMOCommand {
|
|
|
137
138
|
default: false,
|
|
138
139
|
}),
|
|
139
140
|
'create-pr': Flags.boolean({
|
|
140
|
-
description: 'Create PR when work is ready',
|
|
141
|
+
description: 'Create PR when work is ready (canonical flag for PR behavior)',
|
|
141
142
|
default: false,
|
|
142
143
|
}),
|
|
143
144
|
'no-pr': Flags.boolean({
|
|
144
|
-
description: '
|
|
145
|
+
description: '[deprecated: use --create-pr instead] Skip PR creation when work is ready',
|
|
145
146
|
default: false,
|
|
146
147
|
}),
|
|
147
148
|
output: Flags.string({
|
|
@@ -193,6 +194,10 @@ export default class WorkStart extends PMOCommand {
|
|
|
193
194
|
if (flags['create-pr'] && flags['no-pr']) {
|
|
194
195
|
this.error('--create-pr and --no-pr are mutually exclusive');
|
|
195
196
|
}
|
|
197
|
+
// Deprecation guidance for --no-pr
|
|
198
|
+
if (flags['no-pr']) {
|
|
199
|
+
this.warn('--no-pr is deprecated. Omit --create-pr instead (PR creation is off by default). --no-pr will continue to work.');
|
|
200
|
+
}
|
|
196
201
|
// Handle --skip-permissions flag (alias for --permission-mode danger)
|
|
197
202
|
// Check for conflicting flags first
|
|
198
203
|
if (flags['skip-permissions'] && flags['permission-mode']) {
|
|
@@ -272,6 +277,7 @@ export default class WorkStart extends PMOCommand {
|
|
|
272
277
|
if (allFlagsProvided && !flags.yes) {
|
|
273
278
|
// All flags provided but no --yes: return confirmation_needed with plan
|
|
274
279
|
const metadata = createMetadata('work start', flags);
|
|
280
|
+
metadata.resolvedPRMode = flags['create-pr'] ? 'create-pr' : 'no-pr';
|
|
275
281
|
// Build the confirm command with --yes
|
|
276
282
|
let confirmCmd = `prlt work start ${ticketId}`;
|
|
277
283
|
if (flags.action)
|
|
@@ -1281,9 +1287,7 @@ export default class WorkStart extends PMOCommand {
|
|
|
1281
1287
|
this.log(styles.warning(` Permissions: ⚠️ danger (--dangerously-skip-permissions)`));
|
|
1282
1288
|
}
|
|
1283
1289
|
this.log(styles.muted(` Output: ${outputMode === 'interactive' ? 'streaming (watch Claude work)' : 'print (final result only)'}`));
|
|
1284
|
-
|
|
1285
|
-
this.log(styles.muted(` Create PR: ${createPR ? 'yes (when work is ready)' : 'no'}`));
|
|
1286
|
-
}
|
|
1290
|
+
this.log(styles.muted(` PR mode: ${createPR ? 'create-pr' : 'no-pr'}${ghAvailable ? '' : ' (gh CLI not available)'}`));
|
|
1287
1291
|
this.log(styles.muted(` Worktree: ${worktreePath}`));
|
|
1288
1292
|
this.log(styles.muted(` Branch: ${branch}`));
|
|
1289
1293
|
this.log('');
|
|
@@ -1585,7 +1589,9 @@ export default class WorkStart extends PMOCommand {
|
|
|
1585
1589
|
});
|
|
1586
1590
|
// Output results
|
|
1587
1591
|
if (jsonMode) {
|
|
1588
|
-
// Output JSON execution result
|
|
1592
|
+
// Output JSON execution result with resolved PR mode
|
|
1593
|
+
const metadata = createMetadata('work start', flags);
|
|
1594
|
+
metadata.resolvedPRMode = createPR ? 'create-pr' : 'no-pr';
|
|
1589
1595
|
outputExecutionResultAsJson([{
|
|
1590
1596
|
workId: execution.id,
|
|
1591
1597
|
ticketId: ticket.id,
|
|
@@ -1593,7 +1599,7 @@ export default class WorkStart extends PMOCommand {
|
|
|
1593
1599
|
sessionId: result.sessionId,
|
|
1594
1600
|
containerId: result.containerId,
|
|
1595
1601
|
status: 'running',
|
|
1596
|
-
}], 1, 0,
|
|
1602
|
+
}], 1, 0, metadata);
|
|
1597
1603
|
}
|
|
1598
1604
|
else {
|
|
1599
1605
|
this.log('');
|
|
@@ -1608,13 +1614,15 @@ export default class WorkStart extends PMOCommand {
|
|
|
1608
1614
|
else {
|
|
1609
1615
|
executionStorage.updateStatus(execution.id, 'failed');
|
|
1610
1616
|
if (jsonMode) {
|
|
1611
|
-
// Output JSON failure result
|
|
1617
|
+
// Output JSON failure result with resolved PR mode
|
|
1618
|
+
const failMetadata = createMetadata('work start', flags);
|
|
1619
|
+
failMetadata.resolvedPRMode = createPR ? 'create-pr' : 'no-pr';
|
|
1612
1620
|
outputExecutionResultAsJson([{
|
|
1613
1621
|
workId: execution.id,
|
|
1614
1622
|
ticketId: ticket.id,
|
|
1615
1623
|
agent: assignedAgent,
|
|
1616
1624
|
status: 'failed',
|
|
1617
|
-
}], 0, 1,
|
|
1625
|
+
}], 0, 1, failMetadata);
|
|
1618
1626
|
}
|
|
1619
1627
|
else {
|
|
1620
1628
|
this.error(`Failed to start work: ${result.error}`);
|
|
@@ -60,7 +60,7 @@ export default class WorkWatch extends PMOCommand {
|
|
|
60
60
|
default: false,
|
|
61
61
|
}),
|
|
62
62
|
'create-pr': Flags.boolean({
|
|
63
|
-
description: 'Create PR when work is ready',
|
|
63
|
+
description: 'Create PR when work is ready (canonical flag for PR behavior)',
|
|
64
64
|
default: false,
|
|
65
65
|
}),
|
|
66
66
|
};
|
|
@@ -29,10 +29,10 @@ export default class WorkspacePrune extends PromptCommand {
|
|
|
29
29
|
};
|
|
30
30
|
async run() {
|
|
31
31
|
const { flags } = await this.parse(WorkspacePrune);
|
|
32
|
-
// In non-TTY mode
|
|
33
|
-
//
|
|
32
|
+
// In non-TTY mode (CI, scripts, piped), default to dry-run unless --force is set.
|
|
33
|
+
// This applies regardless of output format (text or JSON).
|
|
34
34
|
const nonTTY = isNonTTY();
|
|
35
|
-
const effectiveDryRun = flags['dry-run'] || (
|
|
35
|
+
const effectiveDryRun = flags['dry-run'] || (nonTTY && !flags.force);
|
|
36
36
|
// Find stale entries
|
|
37
37
|
const staleWorkspaces = this.findStaleWorkspaces();
|
|
38
38
|
const staleAgents = this.findStaleAgents();
|
package/dist/hooks/init.js
CHANGED
|
@@ -8,13 +8,24 @@ import { findHQRoot } from '../lib/workspace.js';
|
|
|
8
8
|
* - No workspaces are registered in machine config (~/.proletariat/config.json)
|
|
9
9
|
* - AND they're not currently inside a valid HQ directory
|
|
10
10
|
*/
|
|
11
|
-
const hook = async function ({ id, config }) {
|
|
12
|
-
// Skip for
|
|
13
|
-
|
|
11
|
+
const hook = async function ({ id, argv, config }) {
|
|
12
|
+
// Skip for commands that work without an HQ
|
|
13
|
+
const hqOptionalCommands = ['init', 'commit', 'claude', 'pmo:init'];
|
|
14
|
+
if (id && hqOptionalCommands.some(cmd => id === cmd || id.startsWith(cmd + ':'))) {
|
|
14
15
|
return;
|
|
15
16
|
}
|
|
16
|
-
// Skip when
|
|
17
|
-
|
|
17
|
+
// Skip when running under oclif tooling (manifest, readme generation)
|
|
18
|
+
// These run commands to scan metadata and should not trigger the init flow
|
|
19
|
+
if (process.env.OCLIF_COMPILATION || process.argv[1]?.includes('oclif')) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
// Skip when --help or --version flags are present - these should always be available
|
|
23
|
+
// Check both process.argv (production CLI) and the oclif-provided argv
|
|
24
|
+
// (programmatic invocation via @oclif/test runCommand)
|
|
25
|
+
if (process.argv.includes('--help') || process.argv.includes('-h') ||
|
|
26
|
+
argv?.includes('--help') || argv?.includes('-h') ||
|
|
27
|
+
process.argv.includes('--version') || process.argv.includes('-v') ||
|
|
28
|
+
argv?.includes('--version') || argv?.includes('-v')) {
|
|
18
29
|
return;
|
|
19
30
|
}
|
|
20
31
|
// Skip for help-related commands/flags
|
|
@@ -22,10 +33,10 @@ const hook = async function ({ id, config }) {
|
|
|
22
33
|
if (!id || id === 'help') {
|
|
23
34
|
// Check if this is first-time user running bare `prlt`
|
|
24
35
|
if (!id && isFirstTimeUser()) {
|
|
25
|
-
// Run init command
|
|
36
|
+
// Run init command - in TTY it prompts interactively,
|
|
37
|
+
// in non-TTY it outputs a JSON prompt for the HQ name
|
|
26
38
|
const { run } = await import('@oclif/core');
|
|
27
39
|
await run(['init'], config);
|
|
28
|
-
// Exit after init completes to prevent showing help
|
|
29
40
|
process.exit(0);
|
|
30
41
|
}
|
|
31
42
|
return;
|
|
@@ -33,11 +44,11 @@ const hook = async function ({ id, config }) {
|
|
|
33
44
|
// For all other commands, check if first-time user
|
|
34
45
|
if (isFirstTimeUser()) {
|
|
35
46
|
const chalk = await import('chalk');
|
|
36
|
-
console.log(chalk.default.yellow('\n⚠️ No
|
|
37
|
-
// Run init command
|
|
47
|
+
console.log(chalk.default.yellow('\n⚠️ No headquarters found. Let\'s set one up first.\n'));
|
|
48
|
+
// Run init command - in TTY it prompts interactively,
|
|
49
|
+
// in non-TTY it outputs a JSON prompt for the HQ name
|
|
38
50
|
const { run } = await import('@oclif/core');
|
|
39
51
|
await run(['init'], config);
|
|
40
|
-
// Exit after init - user should re-run their original command
|
|
41
52
|
console.log(chalk.default.blue(`\n✅ Setup complete! You can now run: prlt ${id}\n`));
|
|
42
53
|
process.exit(0);
|
|
43
54
|
}
|
|
@@ -111,6 +111,11 @@ export interface EphemeralAgentResult {
|
|
|
111
111
|
/**
|
|
112
112
|
* Create an ephemeral agent on-demand for a spawn operation.
|
|
113
113
|
* Creates worktree in agents/temp/{name}/
|
|
114
|
+
*
|
|
115
|
+
* Concurrency-safe: if the generated name collides at the DB level
|
|
116
|
+
* (e.g. a parallel process inserted the same name first), we clean up
|
|
117
|
+
* the on-disk artifacts, regenerate a name, and retry up to
|
|
118
|
+
* EPHEMERAL_CREATE_MAX_RETRIES times.
|
|
114
119
|
*/
|
|
115
120
|
export declare function createEphemeralAgent(workspaceInfo: WorkspaceInfo, options?: EphemeralAgentOptions): Promise<EphemeralAgentResult>;
|
|
116
121
|
/**
|