@proletariat/cli 0.3.26 → 0.3.28
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/bin/dev.js +7 -0
- package/bin/run.js +7 -0
- package/dist/commands/action/show.js +7 -1
- package/dist/commands/agent/shell.js +24 -10
- package/dist/commands/branch/list.js +14 -11
- package/dist/commands/branch/validate.js +10 -1
- package/dist/commands/claude.js +12 -40
- package/dist/commands/docker/clean.js +7 -9
- package/dist/commands/docker/index.js +5 -4
- package/dist/commands/docker/list.d.ts +1 -0
- package/dist/commands/docker/list.js +31 -17
- package/dist/commands/docker/status.d.ts +3 -1
- package/dist/commands/docker/status.js +28 -2
- package/dist/commands/docker/sync.js +7 -6
- package/dist/commands/epic/list.js +17 -2
- package/dist/commands/execution/list.js +25 -17
- package/dist/commands/pmo/init.js +22 -3
- package/dist/commands/repo/list.js +14 -8
- package/dist/commands/repo/view.js +2 -1
- package/dist/commands/roadmap/list.js +16 -1
- package/dist/commands/session/health.js +11 -10
- package/dist/commands/session/list.js +15 -8
- package/dist/commands/staff/list.d.ts +3 -1
- package/dist/commands/staff/list.js +15 -1
- package/dist/commands/theme/list.d.ts +3 -0
- package/dist/commands/theme/list.js +25 -0
- package/dist/commands/ticket/complete.js +4 -1
- package/dist/commands/ticket/create.d.ts +1 -0
- package/dist/commands/ticket/create.js +30 -0
- package/dist/commands/ticket/delete.js +3 -3
- package/dist/commands/ticket/edit.js +2 -2
- package/dist/commands/ticket/list.js +24 -5
- package/dist/commands/ticket/move.js +4 -1
- package/dist/commands/ticket/view.js +4 -2
- package/dist/commands/whoami.d.ts +3 -0
- package/dist/commands/whoami.js +22 -5
- package/dist/commands/work/complete.js +2 -2
- package/dist/commands/work/ready.js +2 -2
- package/dist/commands/work/revise.js +2 -2
- package/dist/commands/work/spawn.js +6 -21
- package/dist/commands/work/start.js +10 -25
- package/dist/commands/work/watch.js +57 -33
- package/dist/commands/workspace/prune.d.ts +3 -2
- package/dist/commands/workspace/prune.js +70 -10
- package/dist/lib/agents/commands.js +4 -0
- package/dist/lib/agents/index.js +12 -0
- package/dist/lib/execution/devcontainer.d.ts +4 -0
- package/dist/lib/execution/devcontainer.js +88 -3
- package/dist/lib/mcp/helpers.d.ts +15 -0
- package/dist/lib/mcp/helpers.js +15 -0
- package/dist/lib/mcp/tools/action.js +5 -5
- package/dist/lib/mcp/tools/board.js +7 -7
- package/dist/lib/mcp/tools/category.js +5 -5
- package/dist/lib/mcp/tools/cli-passthrough.js +30 -30
- package/dist/lib/mcp/tools/epic.js +8 -8
- package/dist/lib/mcp/tools/phase.js +7 -7
- package/dist/lib/mcp/tools/project.js +10 -10
- package/dist/lib/mcp/tools/roadmap.js +7 -7
- package/dist/lib/mcp/tools/spec.js +9 -9
- package/dist/lib/mcp/tools/status.js +6 -6
- package/dist/lib/mcp/tools/template.js +6 -6
- package/dist/lib/mcp/tools/ticket.js +19 -19
- package/dist/lib/mcp/tools/view.js +4 -4
- package/dist/lib/mcp/tools/work.js +6 -6
- package/dist/lib/mcp/tools/workflow.js +5 -5
- package/dist/lib/pmo/index.js +4 -0
- package/dist/lib/pmo/storage/base.js +49 -0
- package/dist/lib/pmo/types.d.ts +1 -1
- package/dist/lib/pmo/types.js +1 -0
- package/dist/lib/pr/index.d.ts +5 -0
- package/dist/lib/pr/index.js +69 -0
- package/dist/lib/repos/index.js +4 -0
- package/dist/lib/string-utils.d.ts +10 -0
- package/dist/lib/string-utils.js +16 -0
- package/oclif.manifest.json +3331 -3253
- package/package.json +3 -2
|
@@ -3,6 +3,7 @@ import { pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
|
3
3
|
import { PRIORITIES } from '../../lib/pmo/types.js';
|
|
4
4
|
import { getPMOContext } from '../../lib/pmo/pmo-context.js';
|
|
5
5
|
import { styles, formatPriority, formatCategory, getColumnStyle, getColumnEmoji, divider, getPriorityStyle, } from '../../lib/styles.js';
|
|
6
|
+
import { isNonTTY } from '../../lib/prompt-json.js';
|
|
6
7
|
// Priority order for grouping: P0, P1, P2, P3, None
|
|
7
8
|
const PRIORITY_ORDER = ['P0', 'P1', 'P2', 'P3', 'None'];
|
|
8
9
|
export default class TicketList extends Command {
|
|
@@ -67,6 +68,10 @@ export default class TicketList extends Command {
|
|
|
67
68
|
};
|
|
68
69
|
async run() {
|
|
69
70
|
const { flags } = await this.parse(TicketList);
|
|
71
|
+
// Default format to 'json' in non-TTY environments (piped output, CI, agents)
|
|
72
|
+
if (flags.format === 'table' && isNonTTY()) {
|
|
73
|
+
flags.format = 'json';
|
|
74
|
+
}
|
|
70
75
|
// When --all is set, we don't need to select a specific project
|
|
71
76
|
// Otherwise, use the normal project selection flow
|
|
72
77
|
// Get PMO context - no project selection needed
|
|
@@ -110,10 +115,12 @@ export default class TicketList extends Command {
|
|
|
110
115
|
// Get the project board to validate the column
|
|
111
116
|
const targetProjectId = projectId || (await pmoContext.storage.listProjectSummaries())[0]?.id;
|
|
112
117
|
if (targetProjectId) {
|
|
113
|
-
const board = await pmoContext.storage.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
118
|
+
const board = await pmoContext.storage.getProjectBoard(targetProjectId);
|
|
119
|
+
if (board) {
|
|
120
|
+
const validColumns = board.columns.map(c => c.name);
|
|
121
|
+
if (!validColumns.includes(flags.column)) {
|
|
122
|
+
this.error(`Column "${flags.column}" not found. Valid columns: ${validColumns.join(', ')}`);
|
|
123
|
+
}
|
|
117
124
|
}
|
|
118
125
|
}
|
|
119
126
|
}
|
|
@@ -149,7 +156,19 @@ export default class TicketList extends Command {
|
|
|
149
156
|
this.log(styles.warning('No project found.'));
|
|
150
157
|
return;
|
|
151
158
|
}
|
|
152
|
-
const board = await pmoContext.storage.
|
|
159
|
+
const board = await pmoContext.storage.getProjectBoard(actualProjectId);
|
|
160
|
+
if (!board) {
|
|
161
|
+
// Project doesn't exist (orphaned tickets) - fall back to cross-project view
|
|
162
|
+
this.log(styles.warning(`Project "${actualProjectId}" not found. Showing tickets without board layout.`));
|
|
163
|
+
switch (flags.format) {
|
|
164
|
+
case 'json':
|
|
165
|
+
this.log(JSON.stringify(tickets, null, 2));
|
|
166
|
+
break;
|
|
167
|
+
default:
|
|
168
|
+
this.outputCrossProjectTable(tickets, groupBy);
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
153
172
|
const columns = board.columns.map(col => col.name);
|
|
154
173
|
switch (flags.format) {
|
|
155
174
|
case 'json':
|
|
@@ -216,7 +216,10 @@ export default class TicketMove extends PMOCommand {
|
|
|
216
216
|
this.log(styles.emphasis('📦 Move Multiple Tickets\n'));
|
|
217
217
|
}
|
|
218
218
|
// Get columns
|
|
219
|
-
const board = await this.storage.
|
|
219
|
+
const board = await this.storage.getProjectBoard(projectId);
|
|
220
|
+
if (!board) {
|
|
221
|
+
this.error(`Project "${projectId}" not found. The ticket may belong to an orphaned project.`);
|
|
222
|
+
}
|
|
220
223
|
const columns = board.columns.map(col => col.name);
|
|
221
224
|
// Agent mode config for prompts
|
|
222
225
|
const jsonModeConfig = flags.json ? { flags, commandName: 'ticket move --bulk' } : null;
|
|
@@ -63,11 +63,13 @@ export default class TicketView extends PMOCommand {
|
|
|
63
63
|
if (!ticket) {
|
|
64
64
|
this.error(`Ticket "${ticketId}" not found.`);
|
|
65
65
|
}
|
|
66
|
-
|
|
66
|
+
// Get project board (may be null if project was deleted/orphaned)
|
|
67
|
+
const board = ticket.projectId ? await this.storage.getProjectBoard(ticket.projectId) : null;
|
|
68
|
+
const projectName = board?.name || ticket.projectId || 'Unknown';
|
|
67
69
|
// Display ticket details
|
|
68
70
|
this.log(`\n${styles.header('📄 Ticket')} ${styles.emphasis(ticket.id)}\n`);
|
|
69
71
|
this.log(`${styles.header('Title:')} ${ticket.title}`);
|
|
70
|
-
this.log(`${styles.header('Project:')} ${
|
|
72
|
+
this.log(`${styles.header('Project:')} ${projectName}`);
|
|
71
73
|
this.log(`${styles.header('Status:')} ${ticket.statusName}`);
|
|
72
74
|
this.log(`${styles.header('Priority:')} ${ticket.priority || 'none'}`);
|
|
73
75
|
this.log(`${styles.header('Category:')} ${ticket.category || 'none'}`);
|
|
@@ -2,6 +2,9 @@ import { Command } from '@oclif/core';
|
|
|
2
2
|
export default class Whoami extends Command {
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
};
|
|
5
8
|
run(): Promise<void>;
|
|
6
9
|
private detectAgentName;
|
|
7
10
|
private findWorkspaceRoot;
|
package/dist/commands/whoami.js
CHANGED
|
@@ -4,18 +4,39 @@ import * as fs from 'node:fs';
|
|
|
4
4
|
import { execSync } from 'node:child_process';
|
|
5
5
|
import { colors } from '../lib/colors.js';
|
|
6
6
|
import { getAgentByPath } from '../lib/database/index.js';
|
|
7
|
+
import { shouldOutputJson } from '../lib/prompt-json.js';
|
|
8
|
+
import { machineOutputFlags } from '../lib/pmo/index.js';
|
|
7
9
|
export default class Whoami extends Command {
|
|
8
10
|
static description = 'Show current agent/environment context';
|
|
9
11
|
static examples = [
|
|
10
12
|
'<%= config.bin %> <%= command.id %>',
|
|
11
13
|
];
|
|
14
|
+
static flags = {
|
|
15
|
+
...machineOutputFlags,
|
|
16
|
+
};
|
|
12
17
|
async run() {
|
|
13
|
-
await this.parse(Whoami);
|
|
18
|
+
const { flags } = await this.parse(Whoami);
|
|
19
|
+
const jsonMode = shouldOutputJson(flags);
|
|
14
20
|
const isDevcontainer = process.env.DEVCONTAINER === 'true';
|
|
15
21
|
const agentName = this.detectAgentName();
|
|
16
22
|
const repoName = this.detectRepoName();
|
|
17
23
|
const branch = this.getCurrentBranch();
|
|
18
24
|
const hqPath = process.env.PRLT_HQ_PATH;
|
|
25
|
+
const pmoPath = process.env.PRLT_PMO_PATH;
|
|
26
|
+
const hostPath = process.env.PRLT_HOST_PATH;
|
|
27
|
+
if (jsonMode) {
|
|
28
|
+
this.log(JSON.stringify({
|
|
29
|
+
agent: agentName,
|
|
30
|
+
repository: repoName,
|
|
31
|
+
branch,
|
|
32
|
+
environment: isDevcontainer ? 'devcontainer' : 'host',
|
|
33
|
+
workingDir: process.cwd(),
|
|
34
|
+
hqPath: hqPath || null,
|
|
35
|
+
pmoPath: pmoPath || null,
|
|
36
|
+
hostPath: hostPath || null,
|
|
37
|
+
}, null, 2));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
19
40
|
this.log('');
|
|
20
41
|
this.log(colors.primary('🔍 Proletariat Context'));
|
|
21
42
|
this.log('');
|
|
@@ -41,13 +62,9 @@ export default class Whoami extends Command {
|
|
|
41
62
|
if (hqPath) {
|
|
42
63
|
this.log(` HQ path: ${colors.textMuted(hqPath)}`);
|
|
43
64
|
}
|
|
44
|
-
// Show PMO path if available
|
|
45
|
-
const pmoPath = process.env.PRLT_PMO_PATH;
|
|
46
65
|
if (pmoPath) {
|
|
47
66
|
this.log(` PMO path: ${colors.textMuted(pmoPath)}`);
|
|
48
67
|
}
|
|
49
|
-
// Show host path if available (set in devcontainer for agent identity)
|
|
50
|
-
const hostPath = process.env.PRLT_HOST_PATH;
|
|
51
68
|
if (hostPath) {
|
|
52
69
|
this.log(` Host path: ${colors.textMuted(hostPath)}`);
|
|
53
70
|
}
|
|
@@ -91,8 +91,8 @@ export default class WorkComplete extends PMOCommand {
|
|
|
91
91
|
}
|
|
92
92
|
// Get configured column name (from pmo_settings or default)
|
|
93
93
|
const targetColumnName = getWorkColumnSetting(db, 'done');
|
|
94
|
-
const board = await this.storage.
|
|
95
|
-
const columnNames = board.columns.map(col => col.name);
|
|
94
|
+
const board = ticket.projectId ? await this.storage.getProjectBoard(ticket.projectId) : null;
|
|
95
|
+
const columnNames = board ? board.columns.map(col => col.name) : [];
|
|
96
96
|
const doneColumn = findColumnByName(columnNames, targetColumnName);
|
|
97
97
|
if (!doneColumn) {
|
|
98
98
|
db.close();
|
|
@@ -113,8 +113,8 @@ export default class WorkReady extends PMOCommand {
|
|
|
113
113
|
// Get configured column name (from pmo_settings or default)
|
|
114
114
|
// "ready" moves ticket to Review column (work complete moves to Done)
|
|
115
115
|
const targetColumnName = getWorkColumnSetting(db, 'review');
|
|
116
|
-
const board = await this.storage.
|
|
117
|
-
const columnNames = board.columns.map(col => col.name);
|
|
116
|
+
const board = ticket.projectId ? await this.storage.getProjectBoard(ticket.projectId) : null;
|
|
117
|
+
const columnNames = board ? board.columns.map(col => col.name) : [];
|
|
118
118
|
const reviewColumn = findColumnByName(columnNames, targetColumnName);
|
|
119
119
|
if (!reviewColumn) {
|
|
120
120
|
db.close();
|
|
@@ -304,8 +304,8 @@ export default class WorkRevise extends PMOCommand {
|
|
|
304
304
|
this.log('');
|
|
305
305
|
// Move ticket back to In Progress column
|
|
306
306
|
const inProgressColumnName = getWorkColumnSetting(db, 'in_progress');
|
|
307
|
-
const board = await this.storage.
|
|
308
|
-
const columnNames = board.columns.map(col => col.name);
|
|
307
|
+
const board = ticket.projectId ? await this.storage.getProjectBoard(ticket.projectId) : null;
|
|
308
|
+
const columnNames = board ? board.columns.map(col => col.name) : [];
|
|
309
309
|
const inProgressColumn = findColumnByName(columnNames, inProgressColumnName);
|
|
310
310
|
if (inProgressColumn && ticket.statusName !== inProgressColumn) {
|
|
311
311
|
await this.storage.moveTicket(ticket.projectId, ticket.id, inProgressColumn);
|
|
@@ -743,22 +743,9 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
743
743
|
}
|
|
744
744
|
// Prompt for environment (devcontainer vs host) if devcontainer available and not already set
|
|
745
745
|
if (hasDevcontainer && !batchRunOnHost && !batchDisplay) {
|
|
746
|
-
|
|
747
|
-
const dockerRunning = isDockerRunning();
|
|
748
|
-
const devcontainerCliInstalled = isDevcontainerCliInstalled();
|
|
749
|
-
const devcontainerReady = dockerRunning && devcontainerCliInstalled;
|
|
750
|
-
// Build missing requirements message for devcontainer option
|
|
751
|
-
let devcontainerLabel = '🐳 devcontainer (sandboxed, recommended)';
|
|
752
|
-
if (!devcontainerReady) {
|
|
753
|
-
const missing = [];
|
|
754
|
-
if (!dockerRunning)
|
|
755
|
-
missing.push('Docker');
|
|
756
|
-
if (!devcontainerCliInstalled)
|
|
757
|
-
missing.push('devcontainer CLI');
|
|
758
|
-
devcontainerLabel = `🐳 devcontainer (requires: ${missing.join(', ')})`;
|
|
759
|
-
}
|
|
746
|
+
const devcontainerLabel = '🐳 devcontainer (sandboxed, recommended)';
|
|
760
747
|
const envChoices = [
|
|
761
|
-
{ name: devcontainerLabel, value: 'devcontainer'
|
|
748
|
+
{ name: devcontainerLabel, value: 'devcontainer' },
|
|
762
749
|
{ name: '💻 host (runs directly on your machine)', value: 'host' },
|
|
763
750
|
{ name: '✗ cancel', value: 'cancel' },
|
|
764
751
|
];
|
|
@@ -774,7 +761,7 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
774
761
|
flagName: 'selectedEnvironment',
|
|
775
762
|
type: 'list',
|
|
776
763
|
message: 'Where should agents run?',
|
|
777
|
-
default:
|
|
764
|
+
default: 'devcontainer',
|
|
778
765
|
choices: () => envChoices,
|
|
779
766
|
});
|
|
780
767
|
await envResolver.resolve();
|
|
@@ -791,7 +778,7 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
791
778
|
name: 'selectedEnvironment',
|
|
792
779
|
message: 'Where should agents run?',
|
|
793
780
|
choices: envChoices,
|
|
794
|
-
default:
|
|
781
|
+
default: 'devcontainer',
|
|
795
782
|
},
|
|
796
783
|
], spawnJsonModeConfig);
|
|
797
784
|
if (selectedEnvironment === 'cancel') {
|
|
@@ -800,12 +787,10 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
800
787
|
return;
|
|
801
788
|
}
|
|
802
789
|
if (selectedEnvironment === 'devcontainer') {
|
|
803
|
-
//
|
|
790
|
+
// Dynamically check Docker when selected (user may have started it)
|
|
804
791
|
if (!isDockerRunning()) {
|
|
805
792
|
this.log('');
|
|
806
|
-
this.warn('Docker is not running
|
|
807
|
-
'Docker is required for devcontainer execution.\n' +
|
|
808
|
-
'Please start Docker Desktop or select "host" to run directly on your machine.');
|
|
793
|
+
this.warn('Docker is not running. Please start Docker and try again.');
|
|
809
794
|
this.log('');
|
|
810
795
|
continue;
|
|
811
796
|
}
|
|
@@ -768,22 +768,9 @@ export default class WorkStart extends PMOCommand {
|
|
|
768
768
|
let sandboxed = false; // Whether --dangerously-skip-permissions is NOT used
|
|
769
769
|
if (hasDevcontainer && !flags.display && !flags['run-on-host']) {
|
|
770
770
|
// Agent has devcontainer - prompt for environment choice
|
|
771
|
-
|
|
772
|
-
const dockerRunning = isDockerRunning();
|
|
773
|
-
const devcontainerCliInstalled = isDevcontainerCliInstalled();
|
|
774
|
-
const devcontainerReady = dockerRunning && devcontainerCliInstalled;
|
|
775
|
-
// Build missing requirements message for devcontainer option
|
|
776
|
-
let devcontainerLabel = '🐳 devcontainer (sandboxed, recommended)';
|
|
777
|
-
if (!devcontainerReady) {
|
|
778
|
-
const missing = [];
|
|
779
|
-
if (!dockerRunning)
|
|
780
|
-
missing.push('Docker');
|
|
781
|
-
if (!devcontainerCliInstalled)
|
|
782
|
-
missing.push('devcontainer CLI');
|
|
783
|
-
devcontainerLabel = `🐳 devcontainer (requires: ${missing.join(', ')})`;
|
|
784
|
-
}
|
|
771
|
+
const devcontainerLabel = '🐳 devcontainer (sandboxed, recommended)';
|
|
785
772
|
const envChoices = [
|
|
786
|
-
{ name: devcontainerLabel, value: 'devcontainer'
|
|
773
|
+
{ name: devcontainerLabel, value: 'devcontainer' },
|
|
787
774
|
{ name: '💻 host (runs directly on your machine)', value: 'host' },
|
|
788
775
|
{ name: '✗ cancel', value: 'cancel' },
|
|
789
776
|
];
|
|
@@ -799,7 +786,7 @@ export default class WorkStart extends PMOCommand {
|
|
|
799
786
|
flagName: 'selectedEnvironment',
|
|
800
787
|
type: 'list',
|
|
801
788
|
message: 'Where should the agent run?',
|
|
802
|
-
default:
|
|
789
|
+
default: 'devcontainer',
|
|
803
790
|
choices: () => envChoices,
|
|
804
791
|
});
|
|
805
792
|
await envResolver.resolve();
|
|
@@ -817,7 +804,7 @@ export default class WorkStart extends PMOCommand {
|
|
|
817
804
|
name: 'selectedEnvironment',
|
|
818
805
|
message: 'Where should the agent run?',
|
|
819
806
|
choices: envChoices,
|
|
820
|
-
default:
|
|
807
|
+
default: 'devcontainer',
|
|
821
808
|
},
|
|
822
809
|
], jsonModeConfig);
|
|
823
810
|
if (selectedEnvironment === 'cancel') {
|
|
@@ -826,12 +813,10 @@ export default class WorkStart extends PMOCommand {
|
|
|
826
813
|
return;
|
|
827
814
|
}
|
|
828
815
|
if (selectedEnvironment === 'devcontainer') {
|
|
829
|
-
//
|
|
816
|
+
// Dynamically check Docker when selected (user may have started it)
|
|
830
817
|
if (!isDockerRunning()) {
|
|
831
818
|
this.log('');
|
|
832
|
-
this.warn('Docker is not running
|
|
833
|
-
'Docker is required for devcontainer execution.\n' +
|
|
834
|
-
'Please start Docker Desktop or select "host" to run directly on your machine.');
|
|
819
|
+
this.warn('Docker is not running. Please start Docker and try again.');
|
|
835
820
|
this.log('');
|
|
836
821
|
continue; // Re-prompt for environment selection
|
|
837
822
|
}
|
|
@@ -1452,8 +1437,8 @@ export default class WorkStart extends PMOCommand {
|
|
|
1452
1437
|
// Move ticket to target column based on action's defaultMoveToCategory
|
|
1453
1438
|
// If action has a target category, find the matching column; otherwise use "started" default
|
|
1454
1439
|
const targetCategory = selectedAction?.defaultMoveToCategory || 'started';
|
|
1455
|
-
const board = await this.storage.
|
|
1456
|
-
const columnNames = board.columns.map(col => col.name);
|
|
1440
|
+
const board = ticket.projectId ? await this.storage.getProjectBoard(ticket.projectId) : null;
|
|
1441
|
+
const columnNames = board ? board.columns.map(col => col.name) : [];
|
|
1457
1442
|
// Map category to column type for lookup
|
|
1458
1443
|
const columnType = targetCategory === 'started' ? 'in_progress' :
|
|
1459
1444
|
targetCategory === 'unstarted' ? 'planned' :
|
|
@@ -1868,8 +1853,8 @@ export default class WorkStart extends PMOCommand {
|
|
|
1868
1853
|
}
|
|
1869
1854
|
// Move ticket to In Progress column ONLY after successful spawn
|
|
1870
1855
|
const targetColumnName = getWorkColumnSetting(db, 'in_progress');
|
|
1871
|
-
const board = await this.storage.
|
|
1872
|
-
const columnNames = board.columns.map(col => col.name);
|
|
1856
|
+
const board = ticket.projectId ? await this.storage.getProjectBoard(ticket.projectId) : null;
|
|
1857
|
+
const columnNames = board ? board.columns.map(col => col.name) : [];
|
|
1873
1858
|
const inProgressColumn = findColumnByName(columnNames, targetColumnName);
|
|
1874
1859
|
if (inProgressColumn && ticket.status !== inProgressColumn) {
|
|
1875
1860
|
await this.storage.moveTicket(ticket.projectId, ticket.id, inProgressColumn);
|
|
@@ -161,42 +161,66 @@ export default class WorkWatch extends PMOCommand {
|
|
|
161
161
|
const agentDir = path.join(workspaceInfo.agentsPath, agent.name);
|
|
162
162
|
return hasDevcontainerConfig(agentDir);
|
|
163
163
|
});
|
|
164
|
-
// Docker check
|
|
165
|
-
const dockerRunning = isDockerRunning();
|
|
166
|
-
if (hasDevcontainer && !dockerRunning) {
|
|
167
|
-
this.warn('Docker is not running. Agents will run on host instead of devcontainer.\n' +
|
|
168
|
-
'Start Docker Desktop for sandboxed execution.');
|
|
169
|
-
}
|
|
170
|
-
// Devcontainer CLI check
|
|
171
|
-
const devcontainerCliInstalled = isDevcontainerCliInstalled();
|
|
172
|
-
if (hasDevcontainer && dockerRunning && !devcontainerCliInstalled) {
|
|
173
|
-
this.warn('devcontainer CLI is not installed. Agents will run on host instead of devcontainer.\n' +
|
|
174
|
-
'Install with: npm install -g @devcontainers/cli');
|
|
175
|
-
}
|
|
176
164
|
// Prompt for environment and display mode if not provided
|
|
177
165
|
this.environment = 'host';
|
|
178
166
|
this.displayMode = 'terminal';
|
|
179
167
|
if (!flags.mode) {
|
|
180
|
-
if (hasDevcontainer
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
168
|
+
if (hasDevcontainer) {
|
|
169
|
+
const envChoices = [
|
|
170
|
+
{ name: '🐳 devcontainer (sandboxed, recommended)', value: 'devcontainer' },
|
|
171
|
+
{ name: '💻 host (runs directly on your machine)', value: 'host' },
|
|
172
|
+
];
|
|
173
|
+
if (jsonMode) {
|
|
174
|
+
// In JSON mode, use FlagResolver (outputs prompt and exits)
|
|
175
|
+
const envResolver = new FlagResolver({
|
|
176
|
+
commandName: 'work watch',
|
|
177
|
+
baseCommand: 'prlt work watch',
|
|
178
|
+
jsonMode,
|
|
179
|
+
flags: {},
|
|
180
|
+
});
|
|
181
|
+
envResolver.addPrompt({
|
|
182
|
+
flagName: 'selectedEnvironment',
|
|
183
|
+
type: 'list',
|
|
184
|
+
message: 'Where should agents run?',
|
|
185
|
+
default: 'devcontainer',
|
|
186
|
+
choices: () => envChoices,
|
|
187
|
+
});
|
|
188
|
+
await envResolver.resolve();
|
|
189
|
+
return; // unreachable, but satisfies TypeScript
|
|
190
|
+
}
|
|
191
|
+
// Interactive mode: loop to handle Docker not running
|
|
192
|
+
let environmentSelected = false;
|
|
193
|
+
while (!environmentSelected) {
|
|
194
|
+
// eslint-disable-next-line no-await-in-loop -- Interactive loop with retry on Docker check
|
|
195
|
+
const { selectedEnvironment } = await this.prompt([
|
|
196
|
+
{
|
|
197
|
+
type: 'list',
|
|
198
|
+
name: 'selectedEnvironment',
|
|
199
|
+
message: 'Where should agents run?',
|
|
200
|
+
choices: envChoices,
|
|
201
|
+
default: 'devcontainer',
|
|
202
|
+
},
|
|
203
|
+
], null);
|
|
204
|
+
if (selectedEnvironment === 'devcontainer') {
|
|
205
|
+
// Dynamically check Docker when selected (user may have started it)
|
|
206
|
+
if (!isDockerRunning()) {
|
|
207
|
+
this.log('');
|
|
208
|
+
this.warn('Docker is not running. Please start Docker and try again.');
|
|
209
|
+
this.log('');
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (!isDevcontainerCliInstalled()) {
|
|
213
|
+
this.log('');
|
|
214
|
+
this.warn('devcontainer CLI is not installed.\n' +
|
|
215
|
+
'Install with: npm install -g @devcontainers/cli\n' +
|
|
216
|
+
'Or select "host" to run directly on your machine.');
|
|
217
|
+
this.log('');
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
this.environment = selectedEnvironment;
|
|
222
|
+
environmentSelected = true;
|
|
223
|
+
}
|
|
200
224
|
}
|
|
201
225
|
// Use FlagResolver for display mode
|
|
202
226
|
const displayResolver = new FlagResolver({
|
|
@@ -220,7 +244,7 @@ export default class WorkWatch extends PMOCommand {
|
|
|
220
244
|
}
|
|
221
245
|
else {
|
|
222
246
|
this.displayMode = flags.mode;
|
|
223
|
-
this.environment = hasDevcontainer &&
|
|
247
|
+
this.environment = hasDevcontainer && isDockerRunning() ? 'devcontainer' : 'host';
|
|
224
248
|
}
|
|
225
249
|
// Prompt for execution settings (terminal, output mode, permissions, PR creation)
|
|
226
250
|
const promptResult = await promptExecutionSettings(db, {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default class WorkspacePrune extends
|
|
1
|
+
import { PromptCommand } from '../../lib/prompt-command.js';
|
|
2
|
+
export default class WorkspacePrune extends PromptCommand {
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static flags: {
|
|
6
6
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
8
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
9
|
};
|
|
9
10
|
run(): Promise<void>;
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
|
+
import { PromptCommand } from '../../lib/prompt-command.js';
|
|
4
5
|
import { styles } from '../../lib/styles.js';
|
|
5
6
|
import { getRegisteredHeadquarters, unregisterHeadquarters, } from '../../lib/machine-config.js';
|
|
6
7
|
import { getWorkspaceAgents, removeAgentsFromDatabase, getDatabasePath, } from '../../lib/database/index.js';
|
|
7
|
-
|
|
8
|
+
import { outputConfirmationNeededAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
9
|
+
export default class WorkspacePrune extends PromptCommand {
|
|
8
10
|
static description = 'Remove stale workspace entries and agents with deleted worktrees';
|
|
9
11
|
static examples = [
|
|
10
12
|
'<%= config.bin %> <%= command.id %> --dry-run',
|
|
11
13
|
'<%= config.bin %> <%= command.id %>',
|
|
14
|
+
'<%= config.bin %> <%= command.id %> --force',
|
|
12
15
|
];
|
|
13
16
|
static flags = {
|
|
14
17
|
'dry-run': Flags.boolean({
|
|
@@ -16,6 +19,11 @@ export default class WorkspacePrune extends Command {
|
|
|
16
19
|
description: 'Show what would be removed without removing',
|
|
17
20
|
default: false,
|
|
18
21
|
}),
|
|
22
|
+
force: Flags.boolean({
|
|
23
|
+
char: 'f',
|
|
24
|
+
description: 'Skip confirmation prompt and prune immediately',
|
|
25
|
+
default: false,
|
|
26
|
+
}),
|
|
19
27
|
json: Flags.boolean({
|
|
20
28
|
description: 'Output as JSON',
|
|
21
29
|
default: false,
|
|
@@ -23,6 +31,10 @@ export default class WorkspacePrune extends Command {
|
|
|
23
31
|
};
|
|
24
32
|
async run() {
|
|
25
33
|
const { flags } = await this.parse(WorkspacePrune);
|
|
34
|
+
// In non-TTY mode without --json (CI, scripts, piped), default to dry-run unless --force is set.
|
|
35
|
+
// In --json mode, we use confirmation_needed output instead of auto-dry-run so agents can review and confirm.
|
|
36
|
+
const isNonTTY = !process.stdout.isTTY;
|
|
37
|
+
const effectiveDryRun = flags['dry-run'] || (!flags.json && isNonTTY && !flags.force);
|
|
26
38
|
// Find stale entries
|
|
27
39
|
const staleWorkspaces = this.findStaleWorkspaces();
|
|
28
40
|
const staleAgents = this.findStaleAgents();
|
|
@@ -30,7 +42,7 @@ export default class WorkspacePrune extends Command {
|
|
|
30
42
|
// JSON output
|
|
31
43
|
if (flags.json) {
|
|
32
44
|
const output = {
|
|
33
|
-
dryRun:
|
|
45
|
+
dryRun: effectiveDryRun,
|
|
34
46
|
staleWorkspaces: staleWorkspaces.map(w => ({
|
|
35
47
|
name: w.name,
|
|
36
48
|
path: w.path,
|
|
@@ -40,13 +52,22 @@ export default class WorkspacePrune extends Command {
|
|
|
40
52
|
agentName: a.agentName,
|
|
41
53
|
expectedPath: a.expectedPath,
|
|
42
54
|
})),
|
|
43
|
-
totalRemoved:
|
|
55
|
+
totalRemoved: effectiveDryRun ? 0 : totalStale,
|
|
44
56
|
totalFound: totalStale,
|
|
45
57
|
};
|
|
46
|
-
|
|
47
|
-
|
|
58
|
+
if (!effectiveDryRun && totalStale > 0 && !flags.force) {
|
|
59
|
+
// In JSON mode without --force, output confirmation needed
|
|
60
|
+
outputConfirmationNeededAsJson({
|
|
61
|
+
staleWorkspaces: staleWorkspaces.map(w => ({ name: w.name, path: w.path })),
|
|
62
|
+
staleAgents: staleAgents.map(a => ({ workspaceName: a.workspaceName, agentName: a.agentName })),
|
|
63
|
+
totalFound: totalStale,
|
|
64
|
+
}, 'prlt workspace prune --force --json', `Found ${totalStale} stale entries. Run with --force to remove them.`, createMetadata('workspace prune', flags));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (!effectiveDryRun && totalStale > 0) {
|
|
48
68
|
this.performPrune(staleWorkspaces, staleAgents);
|
|
49
69
|
}
|
|
70
|
+
this.log(JSON.stringify(output, null, 2));
|
|
50
71
|
return;
|
|
51
72
|
}
|
|
52
73
|
// Human-readable output
|
|
@@ -84,7 +105,7 @@ export default class WorkspacePrune extends Command {
|
|
|
84
105
|
}
|
|
85
106
|
// Summary
|
|
86
107
|
this.log('');
|
|
87
|
-
if (
|
|
108
|
+
if (effectiveDryRun) {
|
|
88
109
|
this.log(styles.warning(`[DRY RUN] Would remove:`));
|
|
89
110
|
if (staleWorkspaces.length > 0) {
|
|
90
111
|
this.log(styles.muted(` • ${staleWorkspaces.length} workspace registration(s)`));
|
|
@@ -93,10 +114,15 @@ export default class WorkspacePrune extends Command {
|
|
|
93
114
|
this.log(styles.muted(` • ${staleAgents.length} agent record(s)`));
|
|
94
115
|
}
|
|
95
116
|
this.log('');
|
|
96
|
-
|
|
117
|
+
if (isNonTTY) {
|
|
118
|
+
this.log(styles.muted('Non-TTY environment detected. Run with --force to remove these entries.'));
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
this.log(styles.muted('Run without --dry-run to remove these entries.'));
|
|
122
|
+
}
|
|
97
123
|
}
|
|
98
|
-
else {
|
|
99
|
-
//
|
|
124
|
+
else if (flags.force) {
|
|
125
|
+
// --force: skip confirmation
|
|
100
126
|
this.performPrune(staleWorkspaces, staleAgents);
|
|
101
127
|
this.log(styles.success('Pruned:'));
|
|
102
128
|
if (staleWorkspaces.length > 0) {
|
|
@@ -106,6 +132,40 @@ export default class WorkspacePrune extends Command {
|
|
|
106
132
|
this.log(styles.muted(` • ${staleAgents.length} agent record(s)`));
|
|
107
133
|
}
|
|
108
134
|
}
|
|
135
|
+
else {
|
|
136
|
+
// Interactive confirmation
|
|
137
|
+
const summary = [];
|
|
138
|
+
if (staleWorkspaces.length > 0) {
|
|
139
|
+
summary.push(`${staleWorkspaces.length} workspace registration(s)`);
|
|
140
|
+
}
|
|
141
|
+
if (staleAgents.length > 0) {
|
|
142
|
+
summary.push(`${staleAgents.length} agent record(s)`);
|
|
143
|
+
}
|
|
144
|
+
const choices = [
|
|
145
|
+
{ name: 'Yes', value: true },
|
|
146
|
+
{ name: 'No', value: false },
|
|
147
|
+
];
|
|
148
|
+
const message = `Remove ${summary.join(' and ')}?`;
|
|
149
|
+
const { confirmed } = await this.prompt([{
|
|
150
|
+
type: 'list',
|
|
151
|
+
name: 'confirmed',
|
|
152
|
+
message,
|
|
153
|
+
choices,
|
|
154
|
+
}], { flags: flags, commandName: 'workspace prune' });
|
|
155
|
+
if (confirmed) {
|
|
156
|
+
this.performPrune(staleWorkspaces, staleAgents);
|
|
157
|
+
this.log(styles.success('\nPruned:'));
|
|
158
|
+
if (staleWorkspaces.length > 0) {
|
|
159
|
+
this.log(styles.muted(` • ${staleWorkspaces.length} workspace registration(s)`));
|
|
160
|
+
}
|
|
161
|
+
if (staleAgents.length > 0) {
|
|
162
|
+
this.log(styles.muted(` • ${staleAgents.length} agent record(s)`));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
this.log(styles.muted('\nPrune cancelled.'));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
109
169
|
this.log('');
|
|
110
170
|
}
|
|
111
171
|
findStaleWorkspaces() {
|
|
@@ -8,6 +8,7 @@ import inquirer from 'inquirer';
|
|
|
8
8
|
import { getWorkspaceConfig, getWorkspaceAgents, getWorkspaceRepositories, getAgentWorktrees, addAgentsToDatabase, removeAgentsFromDatabase, addEphemeralAgentToDatabase, getEphemeralAgentNames, getActiveTheme, markAgentCleaned, discoverAgentsOnDisk } from '../database/index.js';
|
|
9
9
|
import { isValidAgentName, getSuggestedAgentNames, generateEphemeralAgentName, getThemePersistentDir, getThemeEphemeralDir, extractBaseName, getAgentBaseName, } from '../themes.js';
|
|
10
10
|
import { createDevcontainerConfig } from '../execution/devcontainer.js';
|
|
11
|
+
import { getGitIdentity } from '../pr/index.js';
|
|
11
12
|
import { getPMOContext } from '../pmo/index.js';
|
|
12
13
|
/**
|
|
13
14
|
* Format a list of agents for display in error messages.
|
|
@@ -508,11 +509,14 @@ export async function createEphemeralAgent(workspaceInfo, options) {
|
|
|
508
509
|
if (!options?.skipDevcontainer) {
|
|
509
510
|
const devcontainerDir = path.join(agentDir, '.devcontainer');
|
|
510
511
|
if (!fs.existsSync(devcontainerDir)) {
|
|
512
|
+
const gitIdentity = getGitIdentity();
|
|
511
513
|
createDevcontainerConfig({
|
|
512
514
|
agentName,
|
|
513
515
|
agentDir,
|
|
514
516
|
repoWorktrees: mountMode === 'worktree' ? workspaceInfo.repositories.map(r => r.name) : undefined,
|
|
515
517
|
mountMode,
|
|
518
|
+
gitUserName: gitIdentity.name || undefined,
|
|
519
|
+
gitUserEmail: gitIdentity.email || undefined,
|
|
516
520
|
});
|
|
517
521
|
}
|
|
518
522
|
}
|