@proletariat/cli 0.3.96 → 0.3.98
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/gc.d.ts +1 -0
- package/dist/commands/gc.js +31 -1
- package/dist/commands/gc.js.map +1 -1
- package/dist/commands/linear/connect.d.ts +5 -0
- package/dist/commands/linear/connect.js +84 -0
- package/dist/commands/linear/connect.js.map +1 -1
- package/dist/commands/orchestrate/index.js +18 -10
- package/dist/commands/orchestrate/index.js.map +1 -1
- package/dist/commands/qa/index.js +1 -1
- package/dist/commands/session/watch.d.ts +1 -0
- package/dist/commands/session/watch.js +46 -2
- package/dist/commands/session/watch.js.map +1 -1
- package/dist/commands/watch/index.d.ts +28 -0
- package/dist/commands/watch/index.js +172 -0
- package/dist/commands/watch/index.js.map +1 -0
- package/dist/commands/work/complete.d.ts +1 -0
- package/dist/commands/work/complete.js +38 -31
- package/dist/commands/work/complete.js.map +1 -1
- package/dist/commands/{ticket/index.d.ts → work/drop.d.ts} +5 -4
- package/dist/commands/work/drop.js +215 -0
- package/dist/commands/work/drop.js.map +1 -0
- package/dist/commands/work/linear.js +1 -1
- package/dist/commands/work/linear.js.map +1 -1
- package/dist/commands/work/ready.d.ts +1 -0
- package/dist/commands/work/ready.js +46 -30
- package/dist/commands/work/ready.js.map +1 -1
- package/dist/commands/work/ship.d.ts +1 -0
- package/dist/commands/work/ship.js +56 -40
- package/dist/commands/work/ship.js.map +1 -1
- package/dist/commands/work/start.d.ts +2 -0
- package/dist/commands/work/start.js +238 -83
- package/dist/commands/work/start.js.map +1 -1
- package/dist/commands/work/stop.d.ts +1 -0
- package/dist/commands/work/stop.js +40 -0
- package/dist/commands/work/stop.js.map +1 -1
- package/dist/lib/agents/commands.js +7 -5
- package/dist/lib/agents/commands.js.map +1 -1
- package/dist/lib/asana/client.d.ts +4 -1
- package/dist/lib/asana/client.js +15 -0
- package/dist/lib/asana/client.js.map +1 -1
- package/dist/lib/asana/index.d.ts +1 -1
- package/dist/lib/asana/types.d.ts +4 -0
- package/dist/lib/database/credential-store.js +1 -0
- package/dist/lib/database/credential-store.js.map +1 -1
- package/dist/lib/database/drizzle-schema.d.ts +17 -0
- package/dist/lib/database/drizzle-schema.js +1 -0
- package/dist/lib/database/drizzle-schema.js.map +1 -1
- package/dist/lib/database/migrations/0019_gc_artifact_cleanup.d.ts +9 -0
- package/dist/lib/database/migrations/0019_gc_artifact_cleanup.js +23 -0
- package/dist/lib/database/migrations/0019_gc_artifact_cleanup.js.map +1 -0
- package/dist/lib/database/migrations/0020_transition_map.d.ts +2 -0
- package/dist/lib/database/migrations/0020_transition_map.js +27 -0
- package/dist/lib/database/migrations/0020_transition_map.js.map +1 -0
- package/dist/lib/database/migrations/index.js +4 -0
- package/dist/lib/database/migrations/index.js.map +1 -1
- package/dist/lib/execution/config.d.ts +10 -0
- package/dist/lib/execution/config.js +24 -0
- package/dist/lib/execution/config.js.map +1 -1
- package/dist/lib/execution/preflight.d.ts +51 -0
- package/dist/lib/execution/preflight.js +278 -0
- package/dist/lib/execution/preflight.js.map +1 -0
- package/dist/lib/execution/runners/prompt-builder.d.ts +6 -0
- package/dist/lib/execution/runners/prompt-builder.js +38 -7
- package/dist/lib/execution/runners/prompt-builder.js.map +1 -1
- package/dist/lib/execution/session-utils.d.ts +23 -0
- package/dist/lib/execution/session-utils.js +69 -0
- package/dist/lib/execution/session-utils.js.map +1 -1
- package/dist/lib/execution/spawner.d.ts +11 -1
- package/dist/lib/execution/spawner.js +44 -16
- package/dist/lib/execution/spawner.js.map +1 -1
- package/dist/lib/execution/storage.d.ts +6 -0
- package/dist/lib/execution/storage.js +18 -0
- package/dist/lib/execution/storage.js.map +1 -1
- package/dist/lib/execution/ticket-refs.d.ts +71 -0
- package/dist/lib/execution/ticket-refs.js +125 -0
- package/dist/lib/execution/ticket-refs.js.map +1 -0
- package/dist/lib/execution/types.d.ts +7 -2
- package/dist/lib/execution/types.js +5 -3
- package/dist/lib/execution/types.js.map +1 -1
- package/dist/lib/external-issues/index.d.ts +1 -0
- package/dist/lib/external-issues/index.js +2 -0
- package/dist/lib/external-issues/index.js.map +1 -1
- package/dist/lib/external-issues/linear.js +1 -1
- package/dist/lib/external-issues/linear.js.map +1 -1
- package/dist/lib/external-issues/ticket-builder.d.ts +21 -0
- package/dist/lib/external-issues/ticket-builder.js +43 -0
- package/dist/lib/external-issues/ticket-builder.js.map +1 -0
- package/dist/lib/external-issues/work-start.js +1 -1
- package/dist/lib/external-issues/work-start.js.map +1 -1
- package/dist/lib/gc/index.d.ts +67 -6
- package/dist/lib/gc/index.js +293 -15
- package/dist/lib/gc/index.js.map +1 -1
- package/dist/lib/github/client.d.ts +84 -0
- package/dist/lib/github/client.js +123 -0
- package/dist/lib/github/client.js.map +1 -0
- package/dist/lib/github/config.d.ts +42 -0
- package/dist/lib/github/config.js +115 -0
- package/dist/lib/github/config.js.map +1 -0
- package/dist/lib/github/types.d.ts +71 -0
- package/dist/lib/github/types.js +50 -0
- package/dist/lib/github/types.js.map +1 -0
- package/dist/lib/jira/client.d.ts +113 -0
- package/dist/lib/jira/client.js +208 -0
- package/dist/lib/jira/client.js.map +1 -0
- package/dist/lib/jira/config.d.ts +9 -0
- package/dist/lib/jira/config.js +30 -0
- package/dist/lib/jira/config.js.map +1 -1
- package/dist/lib/jira/index.d.ts +6 -2
- package/dist/lib/jira/index.js +4 -2
- package/dist/lib/jira/index.js.map +1 -1
- package/dist/lib/jira/types.d.ts +118 -0
- package/dist/lib/jira/types.js +45 -0
- package/dist/lib/jira/types.js.map +1 -0
- package/dist/lib/linear/config.d.ts +10 -0
- package/dist/lib/linear/config.js +33 -0
- package/dist/lib/linear/config.js.map +1 -1
- package/dist/lib/mcp/tools/index.d.ts +0 -2
- package/dist/lib/mcp/tools/index.js +0 -2
- package/dist/lib/mcp/tools/index.js.map +1 -1
- package/dist/lib/orchestrate/index.d.ts +2 -0
- package/dist/lib/orchestrate/index.js +1 -0
- package/dist/lib/orchestrate/index.js.map +1 -1
- package/dist/lib/orchestrate/simple-poller.d.ts +57 -0
- package/dist/lib/orchestrate/simple-poller.js +324 -0
- package/dist/lib/orchestrate/simple-poller.js.map +1 -0
- package/dist/lib/pmo/storage/index.js +16 -5
- package/dist/lib/pmo/storage/index.js.map +1 -1
- package/dist/lib/prompt-json.d.ts +31 -0
- package/dist/lib/prompt-json.js.map +1 -1
- package/dist/lib/providers/asana-provider.d.ts +27 -0
- package/dist/lib/providers/asana-provider.js +426 -0
- package/dist/lib/providers/asana-provider.js.map +1 -0
- package/dist/lib/providers/auto-mapper.d.ts +45 -0
- package/dist/lib/providers/auto-mapper.js +138 -0
- package/dist/lib/providers/auto-mapper.js.map +1 -0
- package/dist/lib/providers/github-provider.d.ts +34 -0
- package/dist/lib/providers/github-provider.js +418 -0
- package/dist/lib/providers/github-provider.js.map +1 -0
- package/dist/lib/providers/index.d.ts +3 -0
- package/dist/lib/providers/index.js +3 -0
- package/dist/lib/providers/index.js.map +1 -1
- package/dist/lib/providers/jira-provider.d.ts +31 -0
- package/dist/lib/providers/jira-provider.js +383 -0
- package/dist/lib/providers/jira-provider.js.map +1 -0
- package/dist/lib/providers/linear-provider.js +6 -7
- package/dist/lib/providers/linear-provider.js.map +1 -1
- package/dist/lib/providers/resolver.js +54 -0
- package/dist/lib/providers/resolver.js.map +1 -1
- package/dist/lib/providers/state-intents.d.ts +20 -0
- package/dist/lib/providers/state-intents.js +61 -7
- package/dist/lib/providers/state-intents.js.map +1 -1
- package/dist/lib/providers/state-resolution.d.ts +15 -11
- package/dist/lib/providers/state-resolution.js +54 -48
- package/dist/lib/providers/state-resolution.js.map +1 -1
- package/dist/lib/providers/transition-map.d.ts +59 -0
- package/dist/lib/providers/transition-map.js +113 -0
- package/dist/lib/providers/transition-map.js.map +1 -0
- package/dist/lib/providers/types.d.ts +1 -1
- package/dist/lib/session/index.d.ts +3 -1
- package/dist/lib/session/index.js +3 -1
- package/dist/lib/session/index.js.map +1 -1
- package/dist/lib/session/tmux-watchdog.d.ts +157 -0
- package/dist/lib/session/tmux-watchdog.js +424 -0
- package/dist/lib/session/tmux-watchdog.js.map +1 -0
- package/dist/lib/session/watcher.d.ts +22 -4
- package/dist/lib/session/watcher.js +66 -8
- package/dist/lib/session/watcher.js.map +1 -1
- package/dist/lib/work-lifecycle/post-execution.js +26 -1
- package/dist/lib/work-lifecycle/post-execution.js.map +1 -1
- package/dist/lib/work-lifecycle/transition.d.ts +73 -0
- package/dist/lib/work-lifecycle/transition.js +138 -0
- package/dist/lib/work-lifecycle/transition.js.map +1 -0
- package/dist/lib/work-source/config.d.ts +1 -1
- package/dist/lib/work-source/config.js +11 -1
- package/dist/lib/work-source/config.js.map +1 -1
- package/oclif.manifest.json +936 -1628
- package/package.json +1 -1
- package/dist/commands/ticket/create.d.ts +0 -44
- package/dist/commands/ticket/create.js +0 -760
- package/dist/commands/ticket/create.js.map +0 -1
- package/dist/commands/ticket/delete.d.ts +0 -17
- package/dist/commands/ticket/delete.js +0 -204
- package/dist/commands/ticket/delete.js.map +0 -1
- package/dist/commands/ticket/edit.d.ts +0 -28
- package/dist/commands/ticket/edit.js +0 -402
- package/dist/commands/ticket/edit.js.map +0 -1
- package/dist/commands/ticket/index.js +0 -74
- package/dist/commands/ticket/index.js.map +0 -1
- package/dist/commands/ticket/list.d.ts +0 -33
- package/dist/commands/ticket/list.js +0 -519
- package/dist/commands/ticket/list.js.map +0 -1
- package/dist/commands/ticket/move.d.ts +0 -27
- package/dist/commands/ticket/move.js +0 -413
- package/dist/commands/ticket/move.js.map +0 -1
- package/dist/commands/ticket/show.d.ts +0 -14
- package/dist/commands/ticket/show.js +0 -110
- package/dist/commands/ticket/show.js.map +0 -1
- package/dist/commands/ticket/update.d.ts +0 -28
- package/dist/commands/ticket/update.js +0 -458
- package/dist/commands/ticket/update.js.map +0 -1
- package/dist/lib/mcp/tools/action.d.ts +0 -6
- package/dist/lib/mcp/tools/action.js +0 -123
- package/dist/lib/mcp/tools/action.js.map +0 -1
- package/dist/lib/mcp/tools/ticket.d.ts +0 -6
- package/dist/lib/mcp/tools/ticket.js +0 -464
- package/dist/lib/mcp/tools/ticket.js.map +0 -1
|
@@ -9,7 +9,8 @@ import { enrichAgentSession } from '../../lib/telemetry/telemetry-bridge.js';
|
|
|
9
9
|
import { registerAgent } from '../../lib/registry/index.js';
|
|
10
10
|
import { shouldOutputJson, outputErrorAsJson, createMetadata, outputConfirmationNeededAsJson, outputExecutionResultAsJson, } from '../../lib/prompt-json.js';
|
|
11
11
|
import { FlagResolver } from '../../lib/flags/index.js';
|
|
12
|
-
import {
|
|
12
|
+
import { findColumnByName, resolveReviewGate } from '../../lib/work-lifecycle/settings.js';
|
|
13
|
+
import { moveTicketByIntent } from '../../lib/work-lifecycle/transition.js';
|
|
13
14
|
import { getTicketExternalMetadata, resolveExternalTicketId } from '../../lib/external-issues/utils.js';
|
|
14
15
|
import { styles } from '../../lib/styles.js';
|
|
15
16
|
import { getWorkspaceInfo, createEphemeralAgent, getTicketTmuxSession, killTmuxSession, findWorktreeForBranch, resolveAgentDir, } from '../../lib/agents/commands.js';
|
|
@@ -17,7 +18,7 @@ import { openWorkspaceDatabase } from '../../lib/database/index.js';
|
|
|
17
18
|
import { generateBranchName, DEFAULT_EXECUTION_CONFIG, } from '../../lib/execution/types.js';
|
|
18
19
|
import { runExecution, isDockerRunning, isGitHubTokenAvailable, isDevcontainerCliInstalled, dockerCredentialsExist, getDockerCredentialInfo, isClaudeExecutor, getExecutorDisplayName } from '../../lib/execution/runners.js';
|
|
19
20
|
import { ExecutionStorage, ContainerStorage } from '../../lib/execution/storage.js';
|
|
20
|
-
import { loadExecutionConfig, getTerminalApp, promptTerminalPreference, getShell, promptShellPreference, hasTerminalPreference, hasShellPreference, getAuthMethod, saveAuthMethod, getCreatePrDefault, getMirrorToPmoDefault, getCleanupPolicy } from '../../lib/execution/config.js';
|
|
21
|
+
import { loadExecutionConfig, getTerminalApp, promptTerminalPreference, getShell, promptShellPreference, hasTerminalPreference, hasShellPreference, getAuthMethod, saveAuthMethod, getCreatePrDefault, getVerifyCiDefault, getMirrorToPmoDefault, getCleanupPolicy } from '../../lib/execution/config.js';
|
|
21
22
|
import { hasDevcontainerConfig } from '../../lib/execution/devcontainer.js';
|
|
22
23
|
import { detectRepoWorktrees, resolveWorktreePath, buildWorkspaceRepos } from '../../lib/execution/context.js';
|
|
23
24
|
import { isGHInstalled, isGHAuthenticated } from '../../lib/pr/index.js';
|
|
@@ -27,12 +28,15 @@ import { buildAsanaMetadata, buildAsanaSpawnContextMessage, buildAsanaTicketDesc
|
|
|
27
28
|
import { buildShortcutMetadata, buildShortcutSpawnContextMessage, buildShortcutTicketDescription, getShortcutStoryByKey, } from '../../lib/external-issues/shortcut.js';
|
|
28
29
|
import { buildTrelloMetadata, buildTrelloSpawnContextMessage, buildTrelloTicketDescription, getTrelloCardById, } from '../../lib/external-issues/trello.js';
|
|
29
30
|
import { resolveMirrorToPmo } from '../../lib/external-issues/work-start.js';
|
|
31
|
+
import { buildTicketFromEnvelope } from '../../lib/external-issues/ticket-builder.js';
|
|
32
|
+
import { TicketRefStore } from '../../lib/execution/ticket-refs.js';
|
|
30
33
|
import { getLinearApiKey, loadLinearConfig } from '../../lib/linear/config.js';
|
|
31
34
|
import { LinearMapper } from '../../lib/linear/mapper.js';
|
|
32
35
|
import { ExternalIssueAdapterError } from '../../lib/external-issues/types.js';
|
|
33
36
|
import { loadDefaultWorkSource, getConnectedIntegrations, isLocalTicketId, } from '../../lib/work-source/index.js';
|
|
34
37
|
import { pruneWorktrees, checkoutBranchSafe } from '../../lib/branch/index.js';
|
|
35
38
|
import { handlePostExecutionTransition } from '../../lib/work-lifecycle/index.js';
|
|
39
|
+
import { runPreflightChecks, formatPreflightReport } from '../../lib/execution/preflight.js';
|
|
36
40
|
/**
|
|
37
41
|
* Try to execute a git command, return true if successful
|
|
38
42
|
*/
|
|
@@ -161,6 +165,7 @@ export default class WorkStart extends PMOCommand {
|
|
|
161
165
|
'<%= config.bin %> <%= command.id %> TKT-001 --review-gate post # Ship then human reviews after',
|
|
162
166
|
'<%= config.bin %> <%= command.id %> PRLT-1085 PRLT-1086 PRLT-1087 --create-pr # Batch spawn in parallel',
|
|
163
167
|
'<%= config.bin %> <%= command.id %> TKT-001 TKT-002 TKT-003 --max-parallel 2 # Limit concurrent spawns',
|
|
168
|
+
'<%= config.bin %> <%= command.id %> TKT-001 --dry-run # Validate environment without spawning',
|
|
164
169
|
];
|
|
165
170
|
static args = {
|
|
166
171
|
ticketId: Args.string({
|
|
@@ -246,6 +251,10 @@ export default class WorkStart extends PMOCommand {
|
|
|
246
251
|
description: '[deprecated: use --create-pr instead] Skip PR creation when work is ready',
|
|
247
252
|
default: false,
|
|
248
253
|
}),
|
|
254
|
+
'verify-ci': Flags.boolean({
|
|
255
|
+
description: 'Agent polls CI after pushing and fixes failures before exiting (PRLT-1126)',
|
|
256
|
+
default: false,
|
|
257
|
+
}),
|
|
249
258
|
output: Flags.string({
|
|
250
259
|
char: 'o',
|
|
251
260
|
description: 'Output mode',
|
|
@@ -317,6 +326,10 @@ export default class WorkStart extends PMOCommand {
|
|
|
317
326
|
description: 'Maximum number of concurrent spawns when starting multiple tickets (default: unlimited)',
|
|
318
327
|
min: 1,
|
|
319
328
|
}),
|
|
329
|
+
'dry-run': Flags.boolean({
|
|
330
|
+
description: 'Validate environment and prerequisites without actually spawning an agent',
|
|
331
|
+
default: false,
|
|
332
|
+
}),
|
|
320
333
|
};
|
|
321
334
|
async findLinkedTicketByEnvelope(projectId, envelope) {
|
|
322
335
|
const tickets = await this.storage.listTickets(projectId);
|
|
@@ -520,6 +533,8 @@ export default class WorkStart extends PMOCommand {
|
|
|
520
533
|
let fromIssueMirror;
|
|
521
534
|
let fromIssueMirrorSource;
|
|
522
535
|
let sourceResolutionMeta;
|
|
536
|
+
// PRLT-1167: When built from envelope without PMO mirror, holds the in-memory ticket
|
|
537
|
+
let envelopeTicket;
|
|
523
538
|
// Handle --from shorthand: parse provider:key into source + key
|
|
524
539
|
let fromFlag = flags.from;
|
|
525
540
|
let fromIssueActive = flags['from-issue'];
|
|
@@ -620,12 +635,33 @@ export default class WorkStart extends PMOCommand {
|
|
|
620
635
|
linkedTicket = await this.createOrUpdateLinkedTicket(projectId, envelope, db);
|
|
621
636
|
await autoExportToBoard(this.pmoPath, this.storage);
|
|
622
637
|
}
|
|
638
|
+
else if (existingLinkedTicket) {
|
|
639
|
+
// Existing PMO ticket found — use it (backward compat)
|
|
640
|
+
linkedTicket = existingLinkedTicket;
|
|
641
|
+
}
|
|
623
642
|
else {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
643
|
+
// PRLT-1167: Build Ticket from envelope directly — no PMO mirror
|
|
644
|
+
linkedTicket = buildTicketFromEnvelope(envelope, projectId);
|
|
645
|
+
envelopeTicket = linkedTicket;
|
|
646
|
+
// Store in ticket_refs so post-execution and other commands can find it
|
|
647
|
+
const ticketRefStore = new TicketRefStore(db);
|
|
648
|
+
ticketRefStore.upsert({
|
|
649
|
+
id: linkedTicket.id,
|
|
650
|
+
provider: envelope.source.name,
|
|
651
|
+
externalId: envelope.source.externalId,
|
|
652
|
+
externalKey: envelope.source.externalKey,
|
|
653
|
+
externalUrl: envelope.source.url,
|
|
654
|
+
title: envelope.title,
|
|
655
|
+
description: envelope.description,
|
|
656
|
+
status: envelope.status,
|
|
657
|
+
priority: envelope.priority,
|
|
658
|
+
category: envelope.category,
|
|
659
|
+
assignee: envelope.assignee,
|
|
660
|
+
projectId,
|
|
661
|
+
});
|
|
662
|
+
if (!jsonMode) {
|
|
663
|
+
this.log(styles.muted(`Ticket ref stored: ${linkedTicket.id} (no PMO mirror)`));
|
|
627
664
|
}
|
|
628
|
-
linkedTicket = existingLinkedTicket;
|
|
629
665
|
}
|
|
630
666
|
ticketId = linkedTicket.id;
|
|
631
667
|
externalIssueContextMessage = buildExternalSpawnContextMessage(envelope, flags.message);
|
|
@@ -651,14 +687,65 @@ export default class WorkStart extends PMOCommand {
|
|
|
651
687
|
}
|
|
652
688
|
ticketId = selected;
|
|
653
689
|
}
|
|
654
|
-
// Get ticket
|
|
655
|
-
const ticket = await this.storage.getTicket(ticketId);
|
|
690
|
+
// Get ticket — use envelope-built ticket when available (PRLT-1167: no PMO mirror)
|
|
691
|
+
const ticket = envelopeTicket ?? await this.storage.getTicket(ticketId);
|
|
656
692
|
if (!ticket) {
|
|
657
693
|
db.close();
|
|
658
694
|
return handleError('TICKET_NOT_FOUND', `Ticket "${ticketId}" not found.`);
|
|
659
695
|
}
|
|
660
696
|
// Use resolved internal ID for all subsequent operations (external keys like PRLT-xxx resolve to TKT-xxx)
|
|
661
697
|
ticketId = ticket.id;
|
|
698
|
+
const isExternalOnly = !!envelopeTicket;
|
|
699
|
+
// --dry-run: validate environment and report issues without spawning
|
|
700
|
+
if (flags['dry-run']) {
|
|
701
|
+
const dryRunEnvironment = flags['run-on-host'] ? 'host' : 'devcontainer';
|
|
702
|
+
const dryRunExecutor = flags.executor || DEFAULT_EXECUTION_CONFIG.defaultExecutor;
|
|
703
|
+
const report = runPreflightChecks({
|
|
704
|
+
environment: dryRunEnvironment,
|
|
705
|
+
executor: dryRunExecutor,
|
|
706
|
+
db,
|
|
707
|
+
ticket: { id: ticket.id, title: ticket.title },
|
|
708
|
+
agentDir: null, // Not yet created during dry-run
|
|
709
|
+
});
|
|
710
|
+
if (jsonMode) {
|
|
711
|
+
const metadata = createMetadata('work start', flags);
|
|
712
|
+
metadata.dryRun = true;
|
|
713
|
+
metadata.environment = dryRunEnvironment;
|
|
714
|
+
metadata.executor = dryRunExecutor;
|
|
715
|
+
const jsonResult = {
|
|
716
|
+
passed: report.passed,
|
|
717
|
+
checks: report.checks.map(c => ({
|
|
718
|
+
name: c.name,
|
|
719
|
+
label: c.label,
|
|
720
|
+
passed: c.passed,
|
|
721
|
+
severity: c.severity,
|
|
722
|
+
message: c.message,
|
|
723
|
+
fix: c.fix ?? null,
|
|
724
|
+
})),
|
|
725
|
+
errors: report.errors.length,
|
|
726
|
+
warnings: report.warnings.length,
|
|
727
|
+
};
|
|
728
|
+
outputExecutionResultAsJson([{ workId: '', ticketId: ticket.id, agent: '', status: report.passed ? 'ready' : 'blocked' }], report.passed ? 1 : 0, report.passed ? 0 : 1, { ...metadata, preflight: jsonResult });
|
|
729
|
+
}
|
|
730
|
+
else {
|
|
731
|
+
this.log('');
|
|
732
|
+
this.log(styles.header(`Dry-run: preflight validation for ${ticket.id}`));
|
|
733
|
+
this.log(styles.muted(` Environment: ${dryRunEnvironment}`));
|
|
734
|
+
this.log(styles.muted(` Executor: ${dryRunExecutor}`));
|
|
735
|
+
this.log('');
|
|
736
|
+
this.log(formatPreflightReport(report));
|
|
737
|
+
this.log('');
|
|
738
|
+
if (report.passed) {
|
|
739
|
+
this.log(styles.success('All preflight checks passed — ready to spawn.'));
|
|
740
|
+
}
|
|
741
|
+
else {
|
|
742
|
+
this.log(styles.error(`${report.errors.length} error(s) and ${report.warnings.length} warning(s) found.`));
|
|
743
|
+
this.log(styles.muted('Fix the errors above, then run without --dry-run to start work.'));
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
db.close();
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
662
749
|
// In JSON mode with explicit flags, implement two-step confirm-then-execute protocol
|
|
663
750
|
if (jsonMode) {
|
|
664
751
|
// Check if all required flags for non-interactive execution are provided
|
|
@@ -745,8 +832,8 @@ export default class WorkStart extends PMOCommand {
|
|
|
745
832
|
// If --yes is set with all flags, continue to execution (don't return)
|
|
746
833
|
// If missing flags, continue and let FlagResolver handle prompts
|
|
747
834
|
}
|
|
748
|
-
// Check if ticket is blocked by dependencies
|
|
749
|
-
const isBlocked = await this.storage.isTicketBlocked(ticketId);
|
|
835
|
+
// Check if ticket is blocked by dependencies (skip for external-only tickets)
|
|
836
|
+
const isBlocked = isExternalOnly ? false : await this.storage.isTicketBlocked(ticketId);
|
|
750
837
|
if (isBlocked && !flags.force) {
|
|
751
838
|
const blockers = await this.storage.getTicketBlockers(ticketId);
|
|
752
839
|
const incompleteBlockers = blockers.filter(b => b.status !== 'done' && b.status !== 'canceled');
|
|
@@ -1783,6 +1870,19 @@ export default class WorkStart extends PMOCommand {
|
|
|
1783
1870
|
}
|
|
1784
1871
|
// Add createPR to context
|
|
1785
1872
|
context.createPR = createPR;
|
|
1873
|
+
// Resolve verify-ci: flag > workspace config > default false
|
|
1874
|
+
// Only meaningful when createPR is true (need a PR to poll CI on)
|
|
1875
|
+
let verifyCi = false;
|
|
1876
|
+
if (flags['verify-ci']) {
|
|
1877
|
+
verifyCi = true;
|
|
1878
|
+
}
|
|
1879
|
+
else {
|
|
1880
|
+
const configVerifyCi = getVerifyCiDefault(db);
|
|
1881
|
+
if (configVerifyCi !== null) {
|
|
1882
|
+
verifyCi = configVerifyCi;
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
context.verifyCi = verifyCi;
|
|
1786
1886
|
// Resolve review gate mode (most specific wins: spawn flag > action config > workspace default)
|
|
1787
1887
|
const reviewGate = resolveReviewGate(flags['review-gate'], selectedAction?.reviewGate, db);
|
|
1788
1888
|
context.reviewGate = reviewGate;
|
|
@@ -1802,6 +1902,10 @@ export default class WorkStart extends PMOCommand {
|
|
|
1802
1902
|
};
|
|
1803
1903
|
this.log(styles.warning(` Review gate: ${reviewGate} (${gateDescriptions[reviewGate]})`));
|
|
1804
1904
|
}
|
|
1905
|
+
// Display verify-ci status
|
|
1906
|
+
if (!jsonMode && verifyCi && context.createPR) {
|
|
1907
|
+
this.log(styles.success(` CI verify: enabled — agent will poll CI and fix failures before exiting`));
|
|
1908
|
+
}
|
|
1805
1909
|
// Handle git operations
|
|
1806
1910
|
let finalBranch = branch;
|
|
1807
1911
|
// Set up repo paths (needed for all action types)
|
|
@@ -1946,8 +2050,8 @@ export default class WorkStart extends PMOCommand {
|
|
|
1946
2050
|
}
|
|
1947
2051
|
}
|
|
1948
2052
|
}
|
|
1949
|
-
// Save branch to ticket
|
|
1950
|
-
if (!isExistingBranch || finalBranch !== branch) {
|
|
2053
|
+
// Save branch to ticket (skip for external-only tickets — no PMO record)
|
|
2054
|
+
if (!isExternalOnly && (!isExistingBranch || finalBranch !== branch)) {
|
|
1951
2055
|
await this.storage.updateTicket(ticket.id, { branch: finalBranch });
|
|
1952
2056
|
}
|
|
1953
2057
|
// Update context with final branch
|
|
@@ -2095,64 +2199,74 @@ export default class WorkStart extends PMOCommand {
|
|
|
2095
2199
|
});
|
|
2096
2200
|
}
|
|
2097
2201
|
// Update ticket assignee ONLY after successful spawn
|
|
2202
|
+
// For external-only tickets, update ticket_ref instead of PMO storage
|
|
2098
2203
|
if (!ticket.assignee || ticket.assignee !== assignedAgent) {
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
// If action has a to_state, use that state name directly; otherwise fall back to "In Progress" default
|
|
2104
|
-
const targetStateName = selectedAction?.toState;
|
|
2105
|
-
const board = ticket.projectId ? await this.storage.getProjectBoard(ticket.projectId) : null;
|
|
2106
|
-
const columnNames = board ? board.columns.map(col => col.name) : [];
|
|
2107
|
-
let targetColumnName = null;
|
|
2108
|
-
if (targetStateName) {
|
|
2109
|
-
// Try direct state name match first
|
|
2110
|
-
targetColumnName = findColumnByName(columnNames, targetStateName);
|
|
2111
|
-
if (!targetColumnName) {
|
|
2112
|
-
const categoryMap = {
|
|
2113
|
-
'Backlog': 'planned',
|
|
2114
|
-
'Todo': 'planned',
|
|
2115
|
-
'In Progress': 'in_progress',
|
|
2116
|
-
'Done': 'done',
|
|
2117
|
-
};
|
|
2118
|
-
const columnType = categoryMap[targetStateName] || 'in_progress';
|
|
2119
|
-
const workColumnName = getWorkColumnSetting(db, columnType);
|
|
2120
|
-
targetColumnName = findColumnByName(columnNames, workColumnName);
|
|
2204
|
+
if (isExternalOnly) {
|
|
2205
|
+
// Update ticket_ref with assignee
|
|
2206
|
+
const ticketRefStore = new TicketRefStore(db);
|
|
2207
|
+
ticketRefStore.upsert({ id: ticket.id, title: ticket.title, assignee: assignedAgent });
|
|
2121
2208
|
}
|
|
2209
|
+
else {
|
|
2210
|
+
await this.storage.updateTicket(ticket.id, { assignee: assignedAgent });
|
|
2211
|
+
}
|
|
2212
|
+
this.log(styles.muted(` Assigned to: ${assignedAgent}`));
|
|
2122
2213
|
}
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2214
|
+
// Move ticket to target column based on action's toState or default 'started' intent
|
|
2215
|
+
// Skip PMO board operations for external-only tickets (no PMO record to move)
|
|
2216
|
+
if (!isExternalOnly) {
|
|
2217
|
+
// If action has a to_state, try direct match first; otherwise use intent resolution
|
|
2218
|
+
const targetStateName = selectedAction?.toState;
|
|
2219
|
+
const board = ticket.projectId ? await this.storage.getProjectBoard(ticket.projectId) : null;
|
|
2220
|
+
const columnNames = board ? board.columns.map(col => col.name) : [];
|
|
2221
|
+
let targetColumnName = null;
|
|
2222
|
+
if (targetStateName) {
|
|
2223
|
+
// Try direct state name match first (backward compat with action toState)
|
|
2224
|
+
targetColumnName = findColumnByName(columnNames, targetStateName);
|
|
2132
2225
|
}
|
|
2133
|
-
|
|
2134
|
-
//
|
|
2135
|
-
|
|
2226
|
+
if (!targetColumnName) {
|
|
2227
|
+
// Use intent-based resolution — 'started' intent
|
|
2228
|
+
const transition = await moveTicketByIntent({
|
|
2229
|
+
db,
|
|
2230
|
+
storage: this.storage,
|
|
2231
|
+
ticket,
|
|
2232
|
+
intent: 'started',
|
|
2233
|
+
providerName: 'pmo',
|
|
2234
|
+
resolveProvider: (tid, pid) => this.resolveTicketProvider(tid, pid),
|
|
2235
|
+
log: (msg) => this.log(styles.muted(` ${msg}`)),
|
|
2236
|
+
});
|
|
2237
|
+
if (transition.moved) {
|
|
2238
|
+
this.log(styles.muted(` Moved to: ${transition.targetColumn}`));
|
|
2239
|
+
}
|
|
2136
2240
|
}
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2241
|
+
else if (targetColumnName && ticket.statusName !== targetColumnName) {
|
|
2242
|
+
try {
|
|
2243
|
+
await this.storage.moveTicket(ticket.projectId, ticket.id, targetColumnName);
|
|
2244
|
+
this.log(styles.muted(` Moved to: ${targetColumnName}`));
|
|
2245
|
+
}
|
|
2246
|
+
catch (moveError) {
|
|
2247
|
+
// Non-fatal - work can proceed even if column move fails
|
|
2248
|
+
this.warn(`Could not move ticket to "${targetColumnName}": ${moveError instanceof Error ? moveError.message : moveError}`);
|
|
2249
|
+
}
|
|
2250
|
+
// Sync to external provider (e.g., Linear) if ticket was imported from one
|
|
2251
|
+
try {
|
|
2252
|
+
const provider = await this.resolveTicketProvider(ticket.id, ticket.projectId);
|
|
2253
|
+
if (provider.name !== 'pmo') {
|
|
2254
|
+
const result = await provider.moveTicket(ticket.id, targetColumnName);
|
|
2255
|
+
if (result.success) {
|
|
2256
|
+
this.log(styles.muted(` Synced to ${result.provider}: ${targetColumnName}`));
|
|
2257
|
+
}
|
|
2144
2258
|
}
|
|
2145
2259
|
}
|
|
2260
|
+
catch {
|
|
2261
|
+
// Non-fatal — don't block work start for provider sync failures
|
|
2262
|
+
}
|
|
2146
2263
|
}
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
this.log(styles.muted(msg));
|
|
2154
|
-
}
|
|
2155
|
-
});
|
|
2264
|
+
await autoExportToBoard(this.pmoPath, this.storage, (msg) => {
|
|
2265
|
+
if (!jsonMode) {
|
|
2266
|
+
this.log(styles.muted(msg));
|
|
2267
|
+
}
|
|
2268
|
+
});
|
|
2269
|
+
} // end if (!isExternalOnly)
|
|
2156
2270
|
// Output results
|
|
2157
2271
|
if (jsonMode) {
|
|
2158
2272
|
// Output JSON execution result with resolved PR mode and source
|
|
@@ -2196,20 +2310,53 @@ export default class WorkStart extends PMOCommand {
|
|
|
2196
2310
|
success: false,
|
|
2197
2311
|
errorType: 'spawn_failure',
|
|
2198
2312
|
});
|
|
2313
|
+
// Run post-failure diagnostics to give user actionable info
|
|
2314
|
+
const failureDiag = runPreflightChecks({
|
|
2315
|
+
environment,
|
|
2316
|
+
executor,
|
|
2317
|
+
db,
|
|
2318
|
+
ticket: { id: ticket.id, title: ticket.title },
|
|
2319
|
+
agentDir: context.agentDir,
|
|
2320
|
+
});
|
|
2321
|
+
const diagErrors = failureDiag.errors;
|
|
2199
2322
|
if (jsonMode) {
|
|
2200
|
-
// Output JSON failure result with resolved PR mode
|
|
2323
|
+
// Output JSON failure result with resolved PR mode and diagnostics
|
|
2201
2324
|
const failMetadata = createMetadata('work start', flags);
|
|
2202
2325
|
failMetadata.resolvedPRMode = createPR ? 'create-pr' : 'no-pr';
|
|
2203
2326
|
failMetadata.prModeSource = prModeSource;
|
|
2327
|
+
failMetadata.spawnError = result.error || 'Unknown error';
|
|
2328
|
+
if (diagErrors.length > 0) {
|
|
2329
|
+
failMetadata.diagnostics = diagErrors.map(e => ({
|
|
2330
|
+
check: e.name,
|
|
2331
|
+
message: e.message,
|
|
2332
|
+
fix: e.fix ?? null,
|
|
2333
|
+
}));
|
|
2334
|
+
}
|
|
2204
2335
|
outputExecutionResultAsJson([{
|
|
2205
2336
|
workId: execution.id,
|
|
2206
2337
|
ticketId: ticket.id,
|
|
2207
2338
|
agent: assignedAgent,
|
|
2208
2339
|
status: 'failed',
|
|
2340
|
+
error: result.error || 'Unknown error',
|
|
2209
2341
|
}], 0, 1, failMetadata);
|
|
2210
2342
|
}
|
|
2211
2343
|
else {
|
|
2212
|
-
|
|
2344
|
+
// Build a detailed error message with the spawn error and any diagnostic findings
|
|
2345
|
+
const spawnError = result.error || 'Unknown error';
|
|
2346
|
+
const errorLines = [`Failed to start work on ${ticket.id}: ${spawnError}`];
|
|
2347
|
+
if (diagErrors.length > 0) {
|
|
2348
|
+
errorLines.push('');
|
|
2349
|
+
errorLines.push('Diagnostics found these issues:');
|
|
2350
|
+
for (const diag of diagErrors) {
|
|
2351
|
+
errorLines.push(` ✗ ${diag.label}: ${diag.message}`);
|
|
2352
|
+
if (diag.fix) {
|
|
2353
|
+
errorLines.push(` → Fix: ${diag.fix}`);
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
errorLines.push('');
|
|
2358
|
+
errorLines.push('Tip: Run with --dry-run to validate your environment before spawning.');
|
|
2359
|
+
return handleError('START_FAILED', errorLines.join('\n'));
|
|
2213
2360
|
}
|
|
2214
2361
|
}
|
|
2215
2362
|
db.close();
|
|
@@ -2289,6 +2436,8 @@ export default class WorkStart extends PMOCommand {
|
|
|
2289
2436
|
startArgs.push('--create-pr');
|
|
2290
2437
|
if (flags['no-pr'])
|
|
2291
2438
|
startArgs.push('--no-pr');
|
|
2439
|
+
if (flags['verify-ci'])
|
|
2440
|
+
startArgs.push('--verify-ci');
|
|
2292
2441
|
if (flags.session)
|
|
2293
2442
|
startArgs.push('--session', flags.session);
|
|
2294
2443
|
if (flags.force)
|
|
@@ -2343,6 +2492,7 @@ export default class WorkStart extends PMOCommand {
|
|
|
2343
2492
|
ticketId: ticket.id,
|
|
2344
2493
|
agent: '',
|
|
2345
2494
|
status: 'failed',
|
|
2495
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2346
2496
|
});
|
|
2347
2497
|
}
|
|
2348
2498
|
};
|
|
@@ -2827,30 +2977,35 @@ export default class WorkStart extends PMOCommand {
|
|
|
2827
2977
|
if (!ticket.assignee || ticket.assignee !== agentName) {
|
|
2828
2978
|
await this.storage.updateTicket(ticket.id, { assignee: agentName });
|
|
2829
2979
|
}
|
|
2830
|
-
// Move ticket to In Progress column ONLY after successful spawn
|
|
2831
|
-
const
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
const provider = await this.resolveTicketProvider(ticket.id, ticket.projectId);
|
|
2840
|
-
if (provider.name !== 'pmo') {
|
|
2841
|
-
await provider.moveTicket(ticket.id, inProgressColumn);
|
|
2842
|
-
}
|
|
2843
|
-
}
|
|
2844
|
-
catch {
|
|
2845
|
-
// Non-fatal — don't block work start for provider sync failures
|
|
2846
|
-
}
|
|
2847
|
-
}
|
|
2980
|
+
// Move ticket to In Progress column ONLY after successful spawn — via intent resolution
|
|
2981
|
+
const transition = await moveTicketByIntent({
|
|
2982
|
+
db,
|
|
2983
|
+
storage: this.storage,
|
|
2984
|
+
ticket,
|
|
2985
|
+
intent: 'started',
|
|
2986
|
+
providerName: 'pmo',
|
|
2987
|
+
resolveProvider: (tid, pid) => this.resolveTicketProvider(tid, pid),
|
|
2988
|
+
});
|
|
2848
2989
|
await autoExportToBoard(this.pmoPath, this.storage, () => { });
|
|
2849
2990
|
this.log(styles.success(` ✓ ${ticket.id} started (${execution.id})`));
|
|
2850
2991
|
}
|
|
2851
2992
|
else {
|
|
2852
2993
|
executionStorage.updateStatus(execution.id, 'failed');
|
|
2853
|
-
|
|
2994
|
+
// Include diagnostic details in the error message
|
|
2995
|
+
const spawnError = result.error || 'Unknown error';
|
|
2996
|
+
const diag = runPreflightChecks({
|
|
2997
|
+
environment,
|
|
2998
|
+
executor,
|
|
2999
|
+
db,
|
|
3000
|
+
ticket: { id: ticket.id, title: ticket.title },
|
|
3001
|
+
agentDir: context.agentDir,
|
|
3002
|
+
});
|
|
3003
|
+
const diagIssues = diag.errors;
|
|
3004
|
+
if (diagIssues.length > 0) {
|
|
3005
|
+
const details = diagIssues.map(e => `${e.label}: ${e.message}`).join('; ');
|
|
3006
|
+
throw new Error(`${spawnError} [${details}]`);
|
|
3007
|
+
}
|
|
3008
|
+
throw new Error(spawnError);
|
|
2854
3009
|
}
|
|
2855
3010
|
}
|
|
2856
3011
|
}
|