@proletariat/cli 0.3.25 → 0.3.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/action/index.js +2 -2
- package/dist/commands/action/show.js +7 -1
- package/dist/commands/agent/auth.js +1 -1
- package/dist/commands/agent/cleanup.js +6 -6
- package/dist/commands/agent/discover.js +1 -1
- package/dist/commands/agent/remove.js +4 -4
- package/dist/commands/autocomplete/setup.d.ts +2 -2
- package/dist/commands/autocomplete/setup.js +5 -5
- package/dist/commands/branch/create.js +31 -30
- package/dist/commands/branch/list.js +14 -11
- package/dist/commands/branch/validate.js +10 -1
- package/dist/commands/category/create.js +4 -5
- package/dist/commands/category/delete.js +2 -3
- package/dist/commands/category/rename.js +2 -3
- package/dist/commands/claude.d.ts +2 -8
- package/dist/commands/claude.js +26 -26
- package/dist/commands/commit.d.ts +2 -8
- package/dist/commands/commit.js +4 -26
- package/dist/commands/config/index.d.ts +2 -10
- package/dist/commands/config/index.js +8 -34
- package/dist/commands/docker/clean.js +7 -9
- package/dist/commands/docker/index.d.ts +2 -2
- package/dist/commands/docker/index.js +13 -12
- 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/delete.js +4 -5
- package/dist/commands/epic/list.js +17 -2
- package/dist/commands/execution/list.js +25 -17
- package/dist/commands/feedback/submit.d.ts +2 -2
- package/dist/commands/feedback/submit.js +9 -9
- package/dist/commands/link/index.js +2 -2
- package/dist/commands/pmo/init.d.ts +2 -2
- package/dist/commands/pmo/init.js +29 -10
- package/dist/commands/project/spec.js +6 -6
- 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.d.ts +29 -0
- package/dist/commands/session/health.js +496 -0
- package/dist/commands/session/index.js +4 -0
- package/dist/commands/session/list.js +15 -8
- package/dist/commands/spec/edit.js +2 -3
- package/dist/commands/staff/add.d.ts +2 -2
- package/dist/commands/staff/add.js +15 -14
- package/dist/commands/staff/index.js +2 -2
- package/dist/commands/staff/list.d.ts +3 -1
- package/dist/commands/staff/list.js +15 -1
- package/dist/commands/staff/remove.js +4 -4
- package/dist/commands/status/index.js +6 -7
- package/dist/commands/template/apply.js +10 -11
- package/dist/commands/template/create.js +18 -17
- package/dist/commands/template/index.d.ts +2 -2
- package/dist/commands/template/index.js +6 -6
- package/dist/commands/template/save.js +8 -7
- package/dist/commands/template/update.js +6 -7
- package/dist/commands/terminal/title.d.ts +2 -26
- package/dist/commands/terminal/title.js +4 -33
- package/dist/commands/theme/index.d.ts +2 -2
- package/dist/commands/theme/index.js +19 -18
- package/dist/commands/theme/list.d.ts +3 -0
- package/dist/commands/theme/list.js +25 -0
- package/dist/commands/theme/set.d.ts +2 -2
- package/dist/commands/theme/set.js +5 -5
- package/dist/commands/ticket/complete.js +4 -1
- package/dist/commands/ticket/create.d.ts +1 -0
- package/dist/commands/ticket/create.js +64 -16
- package/dist/commands/ticket/delete.js +18 -16
- package/dist/commands/ticket/edit.js +22 -14
- package/dist/commands/ticket/epic.js +12 -10
- package/dist/commands/ticket/list.js +24 -5
- package/dist/commands/ticket/move.js +4 -1
- package/dist/commands/ticket/project.js +11 -9
- package/dist/commands/ticket/reassign.js +23 -19
- package/dist/commands/ticket/spec.js +7 -5
- package/dist/commands/ticket/update.js +55 -53
- package/dist/commands/ticket/view.js +4 -2
- package/dist/commands/whoami.d.ts +3 -0
- package/dist/commands/whoami.js +22 -4
- package/dist/commands/work/complete.js +2 -2
- package/dist/commands/work/ready.js +9 -9
- package/dist/commands/work/revise.js +15 -13
- package/dist/commands/work/spawn.js +154 -57
- package/dist/commands/work/start.d.ts +1 -0
- package/dist/commands/work/start.js +299 -177
- package/dist/commands/workspace/prune.d.ts +3 -2
- package/dist/commands/workspace/prune.js +70 -10
- package/dist/hooks/init.js +4 -0
- 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 +63 -0
- 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/pr/index.d.ts +9 -0
- package/dist/lib/pr/index.js +101 -14
- package/dist/lib/prompt-command.d.ts +3 -0
- package/dist/lib/prompt-json.d.ts +72 -1
- package/dist/lib/prompt-json.js +46 -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 +594 -449
- package/package.json +3 -2
|
@@ -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() {
|
package/dist/hooks/init.js
CHANGED
|
@@ -13,6 +13,10 @@ const hook = async function ({ id, config }) {
|
|
|
13
13
|
if (id === 'init') {
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
16
|
+
// Skip when --help flag is present - help should always be available
|
|
17
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
16
20
|
// Skip for help-related commands/flags
|
|
17
21
|
// When user runs just `prlt` with no args, id is undefined
|
|
18
22
|
if (!id || id === 'help') {
|
|
@@ -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
|
}
|
package/dist/lib/agents/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { isValidAgentName, getSuggestedAgentNames, BUILTIN_THEMES, getThemePersi
|
|
|
7
7
|
import { getWorkspaceRepositories, getActiveTheme } from '../database/index.js';
|
|
8
8
|
import { styles } from '../styles.js';
|
|
9
9
|
import { createDevcontainerConfig } from '../execution/devcontainer.js';
|
|
10
|
+
import { getGitIdentity } from '../pr/index.js';
|
|
10
11
|
/**
|
|
11
12
|
* Detect the current agent name from environment or directory structure.
|
|
12
13
|
* Returns null if not running in an agent context.
|
|
@@ -93,6 +94,11 @@ function getRemoteUrl(repoPath) {
|
|
|
93
94
|
export async function createAgentWorktrees(workspacePath, agents, hqPath, options) {
|
|
94
95
|
const mountMode = options?.mountMode || 'worktree'; // Default to worktree for real-time file sync
|
|
95
96
|
const modeLabel = mountMode === 'worktree' ? 'worktree' : 'clone';
|
|
97
|
+
// Detect git identity once for all agents (TKT-934)
|
|
98
|
+
const gitIdentity = getGitIdentity();
|
|
99
|
+
if (!gitIdentity.name && !gitIdentity.email) {
|
|
100
|
+
console.log(chalk.yellow('Warning: Could not detect git identity for devcontainer. Commits may use default identity.'));
|
|
101
|
+
}
|
|
96
102
|
if (hqPath) {
|
|
97
103
|
// HQ mode - create worktrees/clones for all repos in repos/ directory
|
|
98
104
|
const reposDir = path.join(hqPath, 'repos');
|
|
@@ -227,6 +233,8 @@ export async function createAgentWorktrees(workspacePath, agents, hqPath, option
|
|
|
227
233
|
agentDir,
|
|
228
234
|
repoWorktrees: mountMode === 'worktree' && createdRepos.length > 0 ? createdRepos : undefined,
|
|
229
235
|
mountMode,
|
|
236
|
+
gitUserName: gitIdentity.name || undefined,
|
|
237
|
+
gitUserEmail: gitIdentity.email || undefined,
|
|
230
238
|
});
|
|
231
239
|
}
|
|
232
240
|
console.log(chalk.green(`✅ Agent ${agent} created with ${createdRepos.length} ${modeLabel}(s)`));
|
|
@@ -249,6 +257,8 @@ export async function createAgentWorktrees(workspacePath, agents, hqPath, option
|
|
|
249
257
|
agentName: agent,
|
|
250
258
|
agentDir,
|
|
251
259
|
mountMode: mountMode,
|
|
260
|
+
gitUserName: gitIdentity.name || undefined,
|
|
261
|
+
gitUserEmail: gitIdentity.email || undefined,
|
|
252
262
|
});
|
|
253
263
|
}
|
|
254
264
|
console.log(chalk.green(`✅ Placeholder agent ${agent} created`));
|
|
@@ -367,6 +377,8 @@ export async function createAgentWorktrees(workspacePath, agents, hqPath, option
|
|
|
367
377
|
agentDir,
|
|
368
378
|
repoWorktrees: mountMode === 'worktree' ? [repoName] : undefined, // Only pass repos for worktree mode
|
|
369
379
|
mountMode,
|
|
380
|
+
gitUserName: gitIdentity.name || undefined,
|
|
381
|
+
gitUserEmail: gitIdentity.email || undefined,
|
|
370
382
|
});
|
|
371
383
|
}
|
|
372
384
|
console.log(chalk.green(`✅ Agent ${agent} created with ${modeLabel}`));
|
|
@@ -17,6 +17,10 @@ export interface DevcontainerOptions {
|
|
|
17
17
|
prltChannel?: string;
|
|
18
18
|
/** Mount mode: 'worktree' needs parent repo mounts + git wrapper, 'clone' is self-contained */
|
|
19
19
|
mountMode?: MountMode;
|
|
20
|
+
/** Git user.name for commit attribution (detected from gh/git config on host) */
|
|
21
|
+
gitUserName?: string;
|
|
22
|
+
/** Git user.email for commit attribution (detected from gh/git config on host) */
|
|
23
|
+
gitUserEmail?: string;
|
|
20
24
|
}
|
|
21
25
|
export interface DevcontainerJson {
|
|
22
26
|
name: string;
|
|
@@ -106,6 +106,9 @@ export function generateDevcontainerJson(options, config) {
|
|
|
106
106
|
PRLT_HOST_PATH: options.agentDir,
|
|
107
107
|
// Mount mode - allows scripts to know if git wrapper is needed
|
|
108
108
|
PRLT_MOUNT_MODE: mountMode,
|
|
109
|
+
// Git identity for commit attribution (detected from host's gh/git config)
|
|
110
|
+
...(options.gitUserName ? { PRLT_GIT_USER_NAME: options.gitUserName } : {}),
|
|
111
|
+
...(options.gitUserEmail ? { PRLT_GIT_USER_EMAIL: options.gitUserEmail } : {}),
|
|
109
112
|
// /hq/.proletariat/bin contains prlt wrapper with ESM loader for native modules
|
|
110
113
|
PATH: '/hq/.proletariat/bin:/home/node/.npm-global/bin:/usr/local/bin:/usr/bin:/bin',
|
|
111
114
|
},
|
|
@@ -452,6 +455,66 @@ else
|
|
|
452
455
|
echo "Warning: No GitHub token found, git push will require manual auth"
|
|
453
456
|
fi
|
|
454
457
|
|
|
458
|
+
# Configure git user identity for commit attribution
|
|
459
|
+
# Uses env vars set by host (from gh/git config), with fallback detection
|
|
460
|
+
configure_git_identity() {
|
|
461
|
+
local git_name="\${PRLT_GIT_USER_NAME:-}"
|
|
462
|
+
local git_email="\${PRLT_GIT_USER_EMAIL:-}"
|
|
463
|
+
|
|
464
|
+
# Fallback: try gh api user if env vars are empty and gh is authenticated
|
|
465
|
+
if { [ -z "$git_name" ] || [ -z "$git_email" ]; } && command -v gh &> /dev/null && gh auth status &>/dev/null; then
|
|
466
|
+
if [ -z "$git_name" ]; then
|
|
467
|
+
git_name=$(gh api user -q '.name // .login' 2>/dev/null || true)
|
|
468
|
+
fi
|
|
469
|
+
if [ -z "$git_email" ]; then
|
|
470
|
+
git_email=$(gh api user -q '.email // empty' 2>/dev/null || true)
|
|
471
|
+
# Try emails API if public email is not set
|
|
472
|
+
if [ -z "$git_email" ]; then
|
|
473
|
+
git_email=$(gh api user/emails -q '[.[] | select(.primary)] | .[0].email' 2>/dev/null || true)
|
|
474
|
+
fi
|
|
475
|
+
fi
|
|
476
|
+
fi
|
|
477
|
+
|
|
478
|
+
# Fallback: try git config from mounted repos
|
|
479
|
+
if [ -z "$git_name" ] || [ -z "$git_email" ]; then
|
|
480
|
+
for repo_dir in /workspace/*/; do
|
|
481
|
+
if [ -d "$repo_dir/.git" ] || [ -f "$repo_dir/.git" ]; then
|
|
482
|
+
if [ -z "$git_name" ]; then
|
|
483
|
+
git_name=$(/usr/bin/git -C "$repo_dir" config user.name 2>/dev/null || true)
|
|
484
|
+
fi
|
|
485
|
+
if [ -z "$git_email" ]; then
|
|
486
|
+
git_email=$(/usr/bin/git -C "$repo_dir" config user.email 2>/dev/null || true)
|
|
487
|
+
fi
|
|
488
|
+
if [ -n "$git_name" ] && [ -n "$git_email" ]; then
|
|
489
|
+
break
|
|
490
|
+
fi
|
|
491
|
+
fi
|
|
492
|
+
done
|
|
493
|
+
fi
|
|
494
|
+
|
|
495
|
+
# Apply git config
|
|
496
|
+
if [ -n "$git_name" ]; then
|
|
497
|
+
/usr/bin/git config --global user.name "$git_name"
|
|
498
|
+
echo "Git user.name set to: $git_name"
|
|
499
|
+
fi
|
|
500
|
+
if [ -n "$git_email" ]; then
|
|
501
|
+
/usr/bin/git config --global user.email "$git_email"
|
|
502
|
+
echo "Git user.email set to: $git_email"
|
|
503
|
+
fi
|
|
504
|
+
|
|
505
|
+
# Warning if identity could not be determined
|
|
506
|
+
if [ -z "$git_name" ] && [ -z "$git_email" ]; then
|
|
507
|
+
echo "Warning: Could not determine git identity. Commits may use default identity."
|
|
508
|
+
echo " To fix: run 'gh auth login' or set git config user.name/user.email in your repo"
|
|
509
|
+
elif [ -z "$git_name" ]; then
|
|
510
|
+
echo "Warning: Could not determine git user.name"
|
|
511
|
+
elif [ -z "$git_email" ]; then
|
|
512
|
+
echo "Warning: Could not determine git user.email"
|
|
513
|
+
fi
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
configure_git_identity
|
|
517
|
+
|
|
455
518
|
# Check if prlt is already installed globally (via npm from GitHub Packages)
|
|
456
519
|
if command -v prlt &> /dev/null; then
|
|
457
520
|
PRLT_PATH=$(which prlt)
|
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP Helper Functions
|
|
3
3
|
*/
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
|
+
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
7
|
+
import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';
|
|
8
|
+
import type { ServerRequest, ServerNotification } from '@modelcontextprotocol/sdk/types.js';
|
|
4
9
|
import type { Ticket } from '../pmo/types.js';
|
|
5
10
|
import type { McpToolResult } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Register an MCP tool with strict parameter validation.
|
|
13
|
+
*
|
|
14
|
+
* Uses z.object().strict() so that unknown/extra parameters are rejected
|
|
15
|
+
* with a clear error instead of being silently stripped.
|
|
16
|
+
* See: https://github.com/anthropics/proletariat/issues/366
|
|
17
|
+
*/
|
|
18
|
+
export declare function strictTool<T extends Record<string, z.ZodType>>(server: McpServer, name: string, description: string, shape: T, handler: (params: {
|
|
19
|
+
[K in keyof T]: z.infer<T[K]>;
|
|
20
|
+
}, extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => CallToolResult | Promise<CallToolResult>): void;
|
|
6
21
|
export declare function formatTicket(t: Ticket): {
|
|
7
22
|
id: string;
|
|
8
23
|
title: string;
|
package/dist/lib/mcp/helpers.js
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP Helper Functions
|
|
3
3
|
*/
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
/**
|
|
6
|
+
* Register an MCP tool with strict parameter validation.
|
|
7
|
+
*
|
|
8
|
+
* Uses z.object().strict() so that unknown/extra parameters are rejected
|
|
9
|
+
* with a clear error instead of being silently stripped.
|
|
10
|
+
* See: https://github.com/anthropics/proletariat/issues/366
|
|
11
|
+
*/
|
|
12
|
+
export function strictTool(server, name, description, shape, handler) {
|
|
13
|
+
const strictSchema = z.object(shape).strict();
|
|
14
|
+
server.registerTool(name, {
|
|
15
|
+
description,
|
|
16
|
+
inputSchema: strictSchema,
|
|
17
|
+
}, handler);
|
|
18
|
+
}
|
|
4
19
|
export function formatTicket(t) {
|
|
5
20
|
return {
|
|
6
21
|
id: t.id,
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* MCP Action Tools
|
|
3
3
|
*/
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import { errorResponse } from '../helpers.js';
|
|
5
|
+
import { errorResponse, strictTool } from '../helpers.js';
|
|
6
6
|
export function registerActionTools(server, ctx) {
|
|
7
|
-
server
|
|
7
|
+
strictTool(server, 'action_list', 'List work actions', { include_builtin: z.boolean().optional() }, async (params) => {
|
|
8
8
|
try {
|
|
9
9
|
const actions = await ctx.storage.listActions({
|
|
10
10
|
isBuiltin: params.include_builtin ? undefined : false,
|
|
@@ -29,7 +29,7 @@ export function registerActionTools(server, ctx) {
|
|
|
29
29
|
return errorResponse(error);
|
|
30
30
|
}
|
|
31
31
|
});
|
|
32
|
-
server
|
|
32
|
+
strictTool(server, 'action_show', 'Get action details', { id: z.string().describe('Action ID') }, async (params) => {
|
|
33
33
|
try {
|
|
34
34
|
const action = await ctx.storage.getAction(params.id);
|
|
35
35
|
if (!action)
|
|
@@ -45,7 +45,7 @@ export function registerActionTools(server, ctx) {
|
|
|
45
45
|
return errorResponse(error);
|
|
46
46
|
}
|
|
47
47
|
});
|
|
48
|
-
server
|
|
48
|
+
strictTool(server, 'action_create', 'Create a work action', {
|
|
49
49
|
name: z.string().describe('Action name'),
|
|
50
50
|
prompt: z.string().describe('Start prompt'),
|
|
51
51
|
description: z.string().optional(),
|
|
@@ -71,7 +71,7 @@ export function registerActionTools(server, ctx) {
|
|
|
71
71
|
return errorResponse(error);
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
|
-
server
|
|
74
|
+
strictTool(server, 'action_delete', 'Delete an action', { id: z.string().describe('Action ID') }, async (params) => {
|
|
75
75
|
try {
|
|
76
76
|
await ctx.storage.deleteAction(params.id);
|
|
77
77
|
return {
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* MCP Board Tools
|
|
3
3
|
*/
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import { errorResponse } from '../helpers.js';
|
|
5
|
+
import { errorResponse, strictTool } from '../helpers.js';
|
|
6
6
|
export function registerBoardTools(server, ctx) {
|
|
7
|
-
server
|
|
7
|
+
strictTool(server, 'board_show', 'Show the kanban board', { project: z.string().optional().describe('Project ID') }, async (params) => {
|
|
8
8
|
try {
|
|
9
9
|
let projectId = params.project;
|
|
10
10
|
if (!projectId) {
|
|
@@ -43,7 +43,7 @@ export function registerBoardTools(server, ctx) {
|
|
|
43
43
|
return errorResponse(error);
|
|
44
44
|
}
|
|
45
45
|
});
|
|
46
|
-
server
|
|
46
|
+
strictTool(server, 'board_columns', 'Get column names for a project', { project: z.string().optional().describe('Project ID') }, async (params) => {
|
|
47
47
|
try {
|
|
48
48
|
let projectId = params.project;
|
|
49
49
|
if (!projectId) {
|
|
@@ -64,7 +64,7 @@ export function registerBoardTools(server, ctx) {
|
|
|
64
64
|
return errorResponse(error);
|
|
65
65
|
}
|
|
66
66
|
});
|
|
67
|
-
server
|
|
67
|
+
strictTool(server, 'board_create_column', 'Add a new column to the board', {
|
|
68
68
|
project: z.string().describe('Project ID'),
|
|
69
69
|
name: z.string().describe('Column name'),
|
|
70
70
|
position: z.number().optional().describe('Position'),
|
|
@@ -82,7 +82,7 @@ export function registerBoardTools(server, ctx) {
|
|
|
82
82
|
return errorResponse(error);
|
|
83
83
|
}
|
|
84
84
|
});
|
|
85
|
-
server
|
|
85
|
+
strictTool(server, 'board_rename_column', 'Rename a column', {
|
|
86
86
|
project: z.string().describe('Project ID'),
|
|
87
87
|
column_id: z.string().describe('Column ID'),
|
|
88
88
|
name: z.string().describe('New name'),
|
|
@@ -100,7 +100,7 @@ export function registerBoardTools(server, ctx) {
|
|
|
100
100
|
return errorResponse(error);
|
|
101
101
|
}
|
|
102
102
|
});
|
|
103
|
-
server
|
|
103
|
+
strictTool(server, 'board_move_column', 'Reorder a column', {
|
|
104
104
|
project: z.string().describe('Project ID'),
|
|
105
105
|
column_id: z.string().describe('Column ID'),
|
|
106
106
|
position: z.number().describe('New position'),
|
|
@@ -118,7 +118,7 @@ export function registerBoardTools(server, ctx) {
|
|
|
118
118
|
return errorResponse(error);
|
|
119
119
|
}
|
|
120
120
|
});
|
|
121
|
-
server
|
|
121
|
+
strictTool(server, 'board_delete_column', 'Delete a column', {
|
|
122
122
|
project: z.string().describe('Project ID'),
|
|
123
123
|
column_id: z.string().describe('Column ID'),
|
|
124
124
|
cascade: z.boolean().optional().describe('Delete tickets in column'),
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* MCP Category Tools
|
|
3
3
|
*/
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import { errorResponse } from '../helpers.js';
|
|
5
|
+
import { errorResponse, strictTool } from '../helpers.js';
|
|
6
6
|
export function registerCategoryTools(server, ctx) {
|
|
7
|
-
server
|
|
7
|
+
strictTool(server, 'category_list', 'List categories', { type: z.enum(['ticket', 'status']).optional() }, async (params) => {
|
|
8
8
|
try {
|
|
9
9
|
const categories = await ctx.storage.listCategories({ type: params.type });
|
|
10
10
|
return {
|
|
@@ -26,7 +26,7 @@ export function registerCategoryTools(server, ctx) {
|
|
|
26
26
|
return errorResponse(error);
|
|
27
27
|
}
|
|
28
28
|
});
|
|
29
|
-
server
|
|
29
|
+
strictTool(server, 'category_create', 'Create a category', {
|
|
30
30
|
name: z.string().describe('Category name'),
|
|
31
31
|
type: z.enum(['ticket', 'status']).describe('Category type'),
|
|
32
32
|
description: z.string().optional(),
|
|
@@ -50,7 +50,7 @@ export function registerCategoryTools(server, ctx) {
|
|
|
50
50
|
return errorResponse(error);
|
|
51
51
|
}
|
|
52
52
|
});
|
|
53
|
-
server
|
|
53
|
+
strictTool(server, 'category_rename', 'Rename a category', {
|
|
54
54
|
id: z.string().describe('Category ID'),
|
|
55
55
|
name: z.string().describe('New name'),
|
|
56
56
|
}, async (params) => {
|
|
@@ -67,7 +67,7 @@ export function registerCategoryTools(server, ctx) {
|
|
|
67
67
|
return errorResponse(error);
|
|
68
68
|
}
|
|
69
69
|
});
|
|
70
|
-
server
|
|
70
|
+
strictTool(server, 'category_delete', 'Delete a category', { id: z.string().describe('Category ID') }, async (params) => {
|
|
71
71
|
try {
|
|
72
72
|
await ctx.storage.deleteCategory(params.id);
|
|
73
73
|
return {
|