@proletariat/cli 0.3.95 → 0.3.97
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/config/index.js +1 -1
- package/dist/commands/config/index.js.map +1 -1
- package/dist/commands/db/repair.js +160 -35
- package/dist/commands/db/repair.js.map +1 -1
- package/dist/commands/gc.d.ts +14 -0
- package/dist/commands/gc.js +208 -0
- package/dist/commands/gc.js.map +1 -0
- 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/pr/merge.js +1 -1
- package/dist/commands/pr/merge.js.map +1 -1
- package/dist/commands/session/attach.d.ts +3 -3
- package/dist/commands/session/attach.js +37 -79
- package/dist/commands/session/attach.js.map +1 -1
- package/dist/commands/session/exec.js +1 -1
- package/dist/commands/session/exec.js.map +1 -1
- package/dist/commands/session/health.js +1 -1
- package/dist/commands/session/health.js.map +1 -1
- package/dist/commands/session/inspect.js +1 -1
- package/dist/commands/session/inspect.js.map +1 -1
- package/dist/commands/session/list.d.ts +1 -0
- package/dist/commands/session/list.js +111 -78
- package/dist/commands/session/list.js.map +1 -1
- package/dist/commands/session/peek.js +1 -1
- package/dist/commands/session/peek.js.map +1 -1
- package/dist/commands/session/poke.js +1 -1
- package/dist/commands/session/poke.js.map +1 -1
- package/dist/commands/session/prune.js +50 -11
- package/dist/commands/session/prune.js.map +1 -1
- package/dist/commands/session/restart.js +1 -1
- package/dist/commands/session/restart.js.map +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/{action → sync}/index.d.ts +2 -6
- package/dist/commands/sync/index.js +68 -0
- package/dist/commands/sync/index.js.map +1 -0
- package/dist/commands/sync/pause.d.ts +10 -0
- package/dist/commands/sync/pause.js +35 -0
- package/dist/commands/sync/pause.js.map +1 -0
- package/dist/commands/sync/queue.d.ts +10 -0
- package/dist/commands/sync/queue.js +106 -0
- package/dist/commands/sync/queue.js.map +1 -0
- package/dist/commands/sync/resume.d.ts +10 -0
- package/dist/commands/sync/resume.js +34 -0
- package/dist/commands/sync/resume.js.map +1 -0
- package/dist/commands/sync/start.d.ts +11 -0
- package/dist/commands/sync/start.js +73 -0
- package/dist/commands/sync/start.js.map +1 -0
- package/dist/commands/sync/status.d.ts +10 -0
- package/dist/commands/sync/status.js +40 -0
- package/dist/commands/sync/status.js.map +1 -0
- package/dist/commands/sync/stop.d.ts +10 -0
- package/dist/commands/sync/stop.js +39 -0
- package/dist/commands/sync/stop.js.map +1 -0
- package/dist/commands/ticket/create.js +1 -1
- package/dist/commands/ticket/create.js.map +1 -1
- package/dist/commands/ticket/edit.js +1 -1
- package/dist/commands/ticket/edit.js.map +1 -1
- package/dist/commands/ticket/list.js +1 -1
- package/dist/commands/ticket/list.js.map +1 -1
- package/dist/commands/ticket/update.js +1 -1
- package/dist/commands/ticket/update.js.map +1 -1
- package/dist/commands/work/complete.d.ts +1 -0
- package/dist/commands/work/complete.js +27 -25
- package/dist/commands/work/complete.js.map +1 -1
- package/dist/commands/work/drop.d.ts +14 -0
- package/dist/commands/work/drop.js +215 -0
- package/dist/commands/work/drop.js.map +1 -0
- package/dist/commands/work/ready.d.ts +1 -0
- package/dist/commands/work/ready.js +26 -25
- package/dist/commands/work/ready.js.map +1 -1
- package/dist/commands/work/resolve.js +1 -1
- package/dist/commands/work/resolve.js.map +1 -1
- package/dist/commands/work/ship.d.ts +1 -0
- package/dist/commands/work/ship.js +33 -32
- package/dist/commands/work/ship.js.map +1 -1
- package/dist/commands/work/start.d.ts +2 -0
- package/dist/commands/work/start.js +172 -125
- 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/dashboard/data.js +1 -1
- package/dist/lib/dashboard/data.js.map +1 -1
- package/dist/lib/database/db-safety.d.ts +22 -0
- package/dist/lib/database/db-safety.js +91 -4
- package/dist/lib/database/db-safety.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/index.d.ts +1 -1
- package/dist/lib/database/index.js +1 -1
- package/dist/lib/database/index.js.map +1 -1
- package/dist/lib/database/migrations/0017_drop_agent_work_fk.d.ts +13 -0
- package/dist/lib/database/migrations/0017_drop_agent_work_fk.js +85 -0
- package/dist/lib/database/migrations/0017_drop_agent_work_fk.js.map +1 -0
- package/dist/lib/database/migrations/0018_create_ticket_refs.d.ts +11 -0
- package/dist/lib/database/migrations/0018_create_ticket_refs.js +40 -0
- package/dist/lib/database/migrations/0018_create_ticket_refs.js.map +1 -0
- 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 +8 -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/devcontainer.js +1 -1
- package/dist/lib/execution/devcontainer.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/docker-management.d.ts +9 -0
- package/dist/lib/execution/runners/docker-management.js +14 -3
- package/dist/lib/execution/runners/docker-management.js.map +1 -1
- package/dist/lib/execution/runners/prompt-builder.d.ts +6 -0
- package/dist/lib/execution/runners/prompt-builder.js +53 -10
- package/dist/lib/execution/runners/prompt-builder.js.map +1 -1
- package/dist/lib/execution/runners/shared.d.ts +1 -1
- package/dist/lib/execution/runners/shared.js +1 -1
- package/dist/lib/execution/runners/shared.js.map +1 -1
- package/dist/lib/execution/session-utils.d.ts +39 -0
- package/dist/lib/execution/session-utils.js +111 -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 +46 -17
- package/dist/lib/execution/spawner.js.map +1 -1
- package/dist/lib/execution/storage.js +2 -1
- 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/utils.d.ts +25 -0
- package/dist/lib/external-issues/utils.js +32 -0
- package/dist/lib/external-issues/utils.js.map +1 -0
- package/dist/lib/gc/index.d.ts +179 -0
- package/dist/lib/gc/index.js +655 -0
- package/dist/lib/gc/index.js.map +1 -0
- package/dist/lib/mcp/tools/action.js +1 -1
- package/dist/lib/mcp/tools/action.js.map +1 -1
- package/dist/lib/mcp/tools/ticket.js +1 -1
- package/dist/lib/mcp/tools/ticket.js.map +1 -1
- package/dist/lib/orchestrate/actions.js +30 -0
- package/dist/lib/orchestrate/actions.js.map +1 -1
- package/dist/lib/orchestrate/poller.js +3 -3
- package/dist/lib/orchestrate/poller.js.map +1 -1
- package/dist/lib/orchestrate/presets.js +2 -1
- package/dist/lib/orchestrate/presets.js.map +1 -1
- package/dist/lib/orchestrate/types.d.ts +1 -1
- package/dist/lib/orchestrate/types.js +1 -0
- package/dist/lib/orchestrate/types.js.map +1 -1
- package/dist/lib/pmo/index.js +1 -1
- package/dist/lib/pmo/index.js.map +1 -1
- package/dist/lib/pmo/markdown.js +1 -1
- package/dist/lib/pmo/markdown.js.map +1 -1
- package/dist/lib/pmo/schema.d.ts +3 -1
- package/dist/lib/pmo/schema.js +26 -2
- package/dist/lib/pmo/schema.js.map +1 -1
- package/dist/lib/pmo/storage/actions.js +1 -1
- package/dist/lib/pmo/storage/actions.js.map +1 -1
- package/dist/lib/pmo/storage/base.js +37 -15
- package/dist/lib/pmo/storage/base.js.map +1 -1
- package/dist/lib/pmo/storage/projects.js +2 -1
- package/dist/lib/pmo/storage/projects.js.map +1 -1
- package/dist/lib/pmo/storage/statuses.js +1 -1
- package/dist/lib/pmo/storage/statuses.js.map +1 -1
- package/dist/lib/pmo/storage/subtasks.js +1 -1
- package/dist/lib/pmo/storage/subtasks.js.map +1 -1
- package/dist/lib/pmo/storage/templates.js +1 -1
- package/dist/lib/pmo/storage/templates.js.map +1 -1
- package/dist/lib/pmo/storage/tickets.js +2 -1
- package/dist/lib/pmo/storage/tickets.js.map +1 -1
- package/dist/lib/pmo/storage/workflow-rules.js +1 -1
- package/dist/lib/pmo/storage/workflow-rules.js.map +1 -1
- package/dist/lib/pmo/utils.d.ts +6 -189
- package/dist/lib/pmo/utils.js +6 -306
- package/dist/lib/pmo/utils.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/auto-mapper.d.ts +45 -0
- package/dist/lib/providers/auto-mapper.js +115 -0
- package/dist/lib/providers/auto-mapper.js.map +1 -0
- 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/session/heartbeat.js +1 -1
- package/dist/lib/session/heartbeat.js.map +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/sync/daemon-process.d.ts +9 -0
- package/dist/lib/sync/daemon-process.js +184 -0
- package/dist/lib/sync/daemon-process.js.map +1 -0
- package/dist/lib/sync/daemon.d.ts +39 -0
- package/dist/lib/sync/daemon.js +91 -0
- package/dist/lib/sync/daemon.js.map +1 -0
- package/dist/lib/sync/engine.d.ts +38 -0
- package/dist/lib/sync/engine.js +145 -0
- package/dist/lib/sync/engine.js.map +1 -0
- package/dist/lib/sync/merge-queue.d.ts +116 -0
- package/dist/lib/sync/merge-queue.js +321 -0
- package/dist/lib/sync/merge-queue.js.map +1 -0
- package/dist/lib/sync/reconciler.d.ts +44 -0
- package/dist/lib/sync/reconciler.js +88 -0
- package/dist/lib/sync/reconciler.js.map +1 -0
- package/dist/lib/utils/text.d.ts +44 -0
- package/dist/lib/utils/text.js +72 -0
- package/dist/lib/utils/text.js.map +1 -0
- package/dist/lib/work-lifecycle/post-execution.js +1 -1
- package/dist/lib/work-lifecycle/post-execution.js.map +1 -1
- package/dist/lib/work-lifecycle/settings.d.ts +138 -0
- package/dist/lib/work-lifecycle/settings.js +213 -0
- package/dist/lib/work-lifecycle/settings.js.map +1 -0
- package/dist/lib/work-lifecycle/transition.d.ts +73 -0
- package/dist/lib/work-lifecycle/transition.js +124 -0
- package/dist/lib/work-lifecycle/transition.js.map +1 -0
- package/oclif.manifest.json +3235 -3380
- package/package.json +1 -1
- package/dist/commands/action/create.d.ts +0 -26
- package/dist/commands/action/create.js +0 -245
- package/dist/commands/action/create.js.map +0 -1
- package/dist/commands/action/delete.d.ts +0 -18
- package/dist/commands/action/delete.js +0 -78
- package/dist/commands/action/delete.js.map +0 -1
- package/dist/commands/action/index.js +0 -103
- package/dist/commands/action/index.js.map +0 -1
- package/dist/commands/action/list.d.ts +0 -15
- package/dist/commands/action/list.js +0 -90
- package/dist/commands/action/list.js.map +0 -1
- package/dist/commands/action/run.d.ts +0 -20
- package/dist/commands/action/run.js +0 -188
- package/dist/commands/action/run.js.map +0 -1
- package/dist/commands/action/show.d.ts +0 -17
- package/dist/commands/action/show.js +0 -78
- package/dist/commands/action/show.js.map +0 -1
- package/dist/commands/action/update.d.ts +0 -27
- package/dist/commands/action/update.js +0 -288
- package/dist/commands/action/update.js.map +0 -1
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync Engine
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates the reconciliation process: gathers ticket state and PR state,
|
|
5
|
+
* runs the reconciler, and applies corrective actions through providers.
|
|
6
|
+
*
|
|
7
|
+
* This is the "run once" engine. The daemon wraps this with a polling loop.
|
|
8
|
+
*/
|
|
9
|
+
import { resolveTicketProvider } from '../providers/resolver.js';
|
|
10
|
+
import { ExecutionStorage } from '../execution/storage.js';
|
|
11
|
+
import { getPRForBranch, getPRChecks, listOpenPRs, } from '../pr/index.js';
|
|
12
|
+
import { reconcileTicket, } from './reconciler.js';
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Engine
|
|
15
|
+
// =============================================================================
|
|
16
|
+
/**
|
|
17
|
+
* Run a single reconciliation cycle.
|
|
18
|
+
*
|
|
19
|
+
* 1. List all tickets in started/review states
|
|
20
|
+
* 2. For each, check PR status
|
|
21
|
+
* 3. Run reconciler to determine actions
|
|
22
|
+
* 4. Apply actions through providers
|
|
23
|
+
*/
|
|
24
|
+
export async function runSyncCycle(db, storage, projectId, options = {}) {
|
|
25
|
+
const { cwd, log, dryRun } = options;
|
|
26
|
+
const execStorage = new ExecutionStorage(db);
|
|
27
|
+
// Gather all tickets in active states (started = In Progress / In Review)
|
|
28
|
+
const startedTickets = await storage.listTickets(projectId, { statusCategory: 'started' });
|
|
29
|
+
// Also check recently completed tickets to catch any that were manually moved
|
|
30
|
+
// but whose PR state doesn't match
|
|
31
|
+
log?.(`Checking ${startedTickets.length} active ticket(s)...`);
|
|
32
|
+
// Pre-fetch open PRs to reduce API calls
|
|
33
|
+
const openPRs = listOpenPRs(cwd);
|
|
34
|
+
const prByBranch = new Map();
|
|
35
|
+
for (const pr of openPRs) {
|
|
36
|
+
prByBranch.set(pr.headBranch, pr);
|
|
37
|
+
}
|
|
38
|
+
const actions = [];
|
|
39
|
+
const errors = [];
|
|
40
|
+
for (const ticket of startedTickets) {
|
|
41
|
+
try {
|
|
42
|
+
const ctx = buildContext(ticket, prByBranch, execStorage, cwd);
|
|
43
|
+
const action = reconcileTicket(ctx);
|
|
44
|
+
if (action) {
|
|
45
|
+
actions.push(action);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
50
|
+
errors.push({ ticketId: ticket.id, error: msg });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const result = {
|
|
54
|
+
actions,
|
|
55
|
+
checked: startedTickets.length,
|
|
56
|
+
errors,
|
|
57
|
+
};
|
|
58
|
+
// Apply actions
|
|
59
|
+
const applied = [];
|
|
60
|
+
const skipped = [];
|
|
61
|
+
const failed = [];
|
|
62
|
+
for (const action of actions) {
|
|
63
|
+
if (dryRun) {
|
|
64
|
+
skipped.push(action);
|
|
65
|
+
log?.(`[dry-run] Would ${formatAction(action)}`);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (action.type === 'flag_stale') {
|
|
69
|
+
// Stale tickets are logged but not moved
|
|
70
|
+
log?.(`Stale: ${action.ticketId} — ${action.reason}`);
|
|
71
|
+
applied.push(action);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const ticket = await storage.getTicket(action.ticketId);
|
|
76
|
+
if (!ticket) {
|
|
77
|
+
failed.push({ action, error: 'Ticket not found' });
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const provider = resolveTicketProvider(action.ticketId, ticket.projectId ?? projectId, db, storage, ticket.metadata);
|
|
81
|
+
if (action.targetStatus) {
|
|
82
|
+
const moveResult = await provider.moveTicket(action.ticketId, action.targetStatus);
|
|
83
|
+
if (moveResult.success) {
|
|
84
|
+
log?.(`Synced: ${action.ticketId} → ${action.targetStatus} (${action.reason})`);
|
|
85
|
+
applied.push(action);
|
|
86
|
+
// If moving to Done, also mark any running executions as completed
|
|
87
|
+
if (action.type === 'move_to_done') {
|
|
88
|
+
markExecutionsCompleted(execStorage, action.ticketId);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
failed.push({ action, error: moveResult.error ?? 'Move failed' });
|
|
93
|
+
log?.(`Failed: ${action.ticketId} — ${moveResult.error}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
99
|
+
failed.push({ action, error: msg });
|
|
100
|
+
log?.(`Error: ${action.ticketId} — ${msg}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return { result, applied, skipped, failed };
|
|
104
|
+
}
|
|
105
|
+
// =============================================================================
|
|
106
|
+
// Helpers
|
|
107
|
+
// =============================================================================
|
|
108
|
+
function buildContext(ticket, prByBranch, execStorage, cwd) {
|
|
109
|
+
// Look up PR by branch — try ticket.branch first, then external key patterns
|
|
110
|
+
let pr = null;
|
|
111
|
+
let checks = [];
|
|
112
|
+
if (ticket.branch) {
|
|
113
|
+
// First check the pre-fetched open PRs
|
|
114
|
+
pr = prByBranch.get(ticket.branch) ?? null;
|
|
115
|
+
// If not found in open PRs, the PR might be merged/closed — fetch directly
|
|
116
|
+
if (!pr) {
|
|
117
|
+
pr = getPRForBranch(ticket.branch, cwd);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (pr && pr.state === 'OPEN') {
|
|
121
|
+
checks = getPRChecks(pr.number, cwd);
|
|
122
|
+
}
|
|
123
|
+
// Check for active execution
|
|
124
|
+
const hasActiveExecution = !!execStorage.getRunningExecution(ticket.id);
|
|
125
|
+
return { ticket, pr, checks, hasActiveExecution };
|
|
126
|
+
}
|
|
127
|
+
function markExecutionsCompleted(execStorage, ticketId) {
|
|
128
|
+
const running = execStorage.getRunningExecution(ticketId);
|
|
129
|
+
if (running) {
|
|
130
|
+
execStorage.updateStatus(running.id, 'completed');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function formatAction(action) {
|
|
134
|
+
switch (action.type) {
|
|
135
|
+
case 'move_to_done':
|
|
136
|
+
return `move ${action.ticketId} to Done (${action.reason})`;
|
|
137
|
+
case 'move_to_review':
|
|
138
|
+
return `move ${action.ticketId} to Review (${action.reason})`;
|
|
139
|
+
case 'move_to_backlog':
|
|
140
|
+
return `move ${action.ticketId} to Backlog (${action.reason})`;
|
|
141
|
+
case 'flag_stale':
|
|
142
|
+
return `flag ${action.ticketId} as stale (${action.reason})`;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../src/lib/sync/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAA;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,EACL,cAAc,EACd,WAAW,EACX,WAAW,GAEZ,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACL,eAAe,GAIhB,MAAM,iBAAiB,CAAA;AAsBxB,gFAAgF;AAChF,SAAS;AACT,gFAAgF;AAEhF;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,EAAqB,EACrB,OAAqC,EACrC,SAAiB,EACjB,UAA6B,EAAE;IAE/B,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAA;IACpC,MAAM,WAAW,GAAG,IAAI,gBAAgB,CAAC,EAAE,CAAC,CAAA;IAE5C,0EAA0E;IAC1E,MAAM,cAAc,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,SAAS,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,CAAC,CAAA;IAC1F,8EAA8E;IAC9E,mCAAmC;IAEnC,GAAG,EAAE,CAAC,YAAY,cAAc,CAAC,MAAM,sBAAsB,CAAC,CAAA;IAE9D,yCAAyC;IACzC,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAA;IAChC,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAA;IAC5C,KAAK,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;QACzB,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,CAAA;IACnC,CAAC;IAED,MAAM,OAAO,GAAsB,EAAE,CAAA;IACrC,MAAM,MAAM,GAA+C,EAAE,CAAA;IAE7D,KAAK,MAAM,MAAM,IAAI,cAAc,EAAE,CAAC;QACpC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,CAAC,CAAA;YAC9D,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,CAAA;YACnC,IAAI,MAAM,EAAE,CAAC;gBACX,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YACtB,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YAC5D,MAAM,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAA;QAClD,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAoB;QAC9B,OAAO;QACP,OAAO,EAAE,cAAc,CAAC,MAAM;QAC9B,MAAM;KACP,CAAA;IAED,gBAAgB;IAChB,MAAM,OAAO,GAAsB,EAAE,CAAA;IACrC,MAAM,OAAO,GAAsB,EAAE,CAAA;IACrC,MAAM,MAAM,GAAsD,EAAE,CAAA;IAEpE,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,MAAM,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YACpB,GAAG,EAAE,CAAC,mBAAmB,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;YAChD,SAAQ;QACV,CAAC;QAED,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YACjC,yCAAyC;YACzC,GAAG,EAAE,CAAC,UAAU,MAAM,CAAC,QAAQ,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC,CAAA;YACrD,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YACpB,SAAQ;QACV,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;YACvD,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAA;gBAClD,SAAQ;YACV,CAAC;YAED,MAAM,QAAQ,GAAG,qBAAqB,CACpC,MAAM,CAAC,QAAQ,EACf,MAAM,CAAC,SAAS,IAAI,SAAS,EAC7B,EAAE,EACF,OAAO,EACP,MAAM,CAAC,QAAQ,CAChB,CAAA;YAED,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;gBACxB,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,YAAY,CAAC,CAAA;gBAClF,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;oBACvB,GAAG,EAAE,CAAC,WAAW,MAAM,CAAC,QAAQ,MAAM,MAAM,CAAC,YAAY,KAAK,MAAM,CAAC,MAAM,GAAG,CAAC,CAAA;oBAC/E,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;oBAEpB,mEAAmE;oBACnE,IAAI,MAAM,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;wBACnC,uBAAuB,CAAC,WAAW,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAA;oBACvD,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,IAAI,aAAa,EAAE,CAAC,CAAA;oBACjE,GAAG,EAAE,CAAC,WAAW,MAAM,CAAC,QAAQ,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC,CAAA;gBAC3D,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YAC5D,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAA;YACnC,GAAG,EAAE,CAAC,UAAU,MAAM,CAAC,QAAQ,MAAM,GAAG,EAAE,CAAC,CAAA;QAC7C,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAA;AAC7C,CAAC;AAED,gFAAgF;AAChF,UAAU;AACV,gFAAgF;AAEhF,SAAS,YAAY,CACnB,MAAc,EACd,UAA+B,EAC/B,WAA6B,EAC7B,GAAY;IAEZ,6EAA6E;IAC7E,IAAI,EAAE,GAAkB,IAAI,CAAA;IAC5B,IAAI,MAAM,GAAuC,EAAE,CAAA;IAEnD,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,uCAAuC;QACvC,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,CAAA;QAE1C,2EAA2E;QAC3E,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,EAAE,GAAG,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QACzC,CAAC;IACH,CAAC;IAED,IAAI,EAAE,IAAI,EAAE,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC;QAC9B,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACtC,CAAC;IAED,6BAA6B;IAC7B,MAAM,kBAAkB,GAAG,CAAC,CAAC,WAAW,CAAC,mBAAmB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IAEvE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAA;AACnD,CAAC;AAED,SAAS,uBAAuB,CAAC,WAA6B,EAAE,QAAgB;IAC9E,MAAM,OAAO,GAAG,WAAW,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAA;IACzD,IAAI,OAAO,EAAE,CAAC;QACZ,WAAW,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,EAAE,WAAW,CAAC,CAAA;IACnD,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,MAAuB;IAC3C,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,cAAc;YACjB,OAAO,QAAQ,MAAM,CAAC,QAAQ,aAAa,MAAM,CAAC,MAAM,GAAG,CAAA;QAC7D,KAAK,gBAAgB;YACnB,OAAO,QAAQ,MAAM,CAAC,QAAQ,eAAe,MAAM,CAAC,MAAM,GAAG,CAAA;QAC/D,KAAK,iBAAiB;YACpB,OAAO,QAAQ,MAAM,CAAC,QAAQ,gBAAgB,MAAM,CAAC,MAAM,GAAG,CAAA;QAChE,KAAK,YAAY;YACf,OAAO,QAAQ,MAAM,CAAC,QAAQ,cAAc,MAAM,CAAC,MAAM,GAAG,CAAA;IAChE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge Queue
|
|
3
|
+
*
|
|
4
|
+
* Sequential rebase-test-merge pipeline for parallel agent PRs.
|
|
5
|
+
*
|
|
6
|
+
* With strict branch protection (PR must be up-to-date with main), parallel
|
|
7
|
+
* agent PRs create a rebase treadmill. The merge queue processes green PRs
|
|
8
|
+
* one at a time: rebase on main → wait for CI → merge if green → eject if red.
|
|
9
|
+
*
|
|
10
|
+
* State machine per cycle:
|
|
11
|
+
* 1. If paused → skip
|
|
12
|
+
* 2. If a PR is currently being processed (rebased, waiting for CI):
|
|
13
|
+
* - CI green → merge, rebase siblings
|
|
14
|
+
* - CI failed → eject (label + comment)
|
|
15
|
+
* - CI pending → wait
|
|
16
|
+
* 3. If no current PR:
|
|
17
|
+
* - Build queue: open, non-draft PRs with green CI, ordered by priority then age
|
|
18
|
+
* - Pick first → rebase → set as current
|
|
19
|
+
* - If rebase fails (conflicts) → eject, try next
|
|
20
|
+
*/
|
|
21
|
+
import type { PRInfo } from '../pr/index.js';
|
|
22
|
+
import { listOpenPRs, getPRChecks, getPRByNumber, mergePR } from '../pr/index.js';
|
|
23
|
+
import { rebaseSiblingPRs } from '../shipping/rebase.js';
|
|
24
|
+
export interface MergeQueueState {
|
|
25
|
+
/** Whether the merge queue is paused */
|
|
26
|
+
paused: boolean;
|
|
27
|
+
/** ISO timestamp when paused */
|
|
28
|
+
pausedAt: string | null;
|
|
29
|
+
/** PR currently being processed (rebased, waiting for CI) */
|
|
30
|
+
currentPR: CurrentPRState | null;
|
|
31
|
+
/** Last PR that was successfully merged */
|
|
32
|
+
lastMergedPR: MergedPRInfo | null;
|
|
33
|
+
/** ISO timestamp of last cycle */
|
|
34
|
+
lastCycleAt: string | null;
|
|
35
|
+
/** Recently ejected PRs (max 10) */
|
|
36
|
+
ejectedPRs: EjectedPRInfo[];
|
|
37
|
+
}
|
|
38
|
+
export interface CurrentPRState {
|
|
39
|
+
prNumber: number;
|
|
40
|
+
headBranch: string;
|
|
41
|
+
baseBranch: string;
|
|
42
|
+
ticketId: string | null;
|
|
43
|
+
priority: number;
|
|
44
|
+
rebasedAt: string;
|
|
45
|
+
}
|
|
46
|
+
export interface MergedPRInfo {
|
|
47
|
+
prNumber: number;
|
|
48
|
+
headBranch: string;
|
|
49
|
+
mergedAt: string;
|
|
50
|
+
}
|
|
51
|
+
export interface EjectedPRInfo {
|
|
52
|
+
prNumber: number;
|
|
53
|
+
headBranch: string;
|
|
54
|
+
reason: string;
|
|
55
|
+
ejectedAt: string;
|
|
56
|
+
}
|
|
57
|
+
export interface MergeQueueEntry {
|
|
58
|
+
pr: PRInfo;
|
|
59
|
+
ticketId: string | null;
|
|
60
|
+
priority: number;
|
|
61
|
+
}
|
|
62
|
+
export interface MergeQueueCycleResult {
|
|
63
|
+
action: 'none' | 'rebase_started' | 'waiting_ci' | 'merged' | 'ejected' | 'paused' | 'error';
|
|
64
|
+
prNumber?: number;
|
|
65
|
+
reason?: string;
|
|
66
|
+
queueSize: number;
|
|
67
|
+
}
|
|
68
|
+
export interface MergeQueueOptions {
|
|
69
|
+
/** HQ path for state file */
|
|
70
|
+
hqPath: string;
|
|
71
|
+
/** Working directory for git/gh operations */
|
|
72
|
+
cwd?: string;
|
|
73
|
+
/** Logging callback */
|
|
74
|
+
log?: (msg: string) => void;
|
|
75
|
+
/** Merge method (default: squash) */
|
|
76
|
+
mergeMethod?: 'merge' | 'squash' | 'rebase';
|
|
77
|
+
/** Delete branch after merging (default: true) */
|
|
78
|
+
deleteBranch?: boolean;
|
|
79
|
+
/** Map of PR head branch to priority (0=P0, 1=P1, 2=P2, 3=P3). Lower = higher priority */
|
|
80
|
+
branchPriorities?: Map<string, number>;
|
|
81
|
+
}
|
|
82
|
+
/** Injectable dependencies for testing */
|
|
83
|
+
export interface MergeQueueDeps {
|
|
84
|
+
listOpenPRs: typeof listOpenPRs;
|
|
85
|
+
getPRChecks: typeof getPRChecks;
|
|
86
|
+
getPRByNumber: typeof getPRByNumber;
|
|
87
|
+
mergePR: typeof mergePR;
|
|
88
|
+
rebaseSiblingPRs: typeof rebaseSiblingPRs;
|
|
89
|
+
updatePRBranch: (prNumber: number, cwd?: string) => import('../shipping/types.js').UpdateBranchResult;
|
|
90
|
+
addLabel: (prNumber: number, label: string, cwd?: string) => boolean;
|
|
91
|
+
addComment: (prNumber: number, body: string, cwd?: string) => boolean;
|
|
92
|
+
}
|
|
93
|
+
export declare function getMergeQueueStatePath(hqPath: string): string;
|
|
94
|
+
export declare function getDefaultState(): MergeQueueState;
|
|
95
|
+
export declare function getMergeQueueState(hqPath: string): MergeQueueState;
|
|
96
|
+
export declare function saveMergeQueueState(hqPath: string, state: MergeQueueState): void;
|
|
97
|
+
export declare function isMergeQueuePaused(hqPath: string): boolean;
|
|
98
|
+
export declare function pauseMergeQueue(hqPath: string): MergeQueueState;
|
|
99
|
+
export declare function resumeMergeQueue(hqPath: string): MergeQueueState;
|
|
100
|
+
/**
|
|
101
|
+
* Build the merge queue: open, non-draft PRs with all CI checks green,
|
|
102
|
+
* ordered by priority (P0 first) then by creation date (oldest first).
|
|
103
|
+
*/
|
|
104
|
+
export declare function buildMergeQueue(options: {
|
|
105
|
+
cwd?: string;
|
|
106
|
+
branchPriorities?: Map<string, number>;
|
|
107
|
+
}, deps?: Pick<MergeQueueDeps, 'listOpenPRs' | 'getPRChecks'>): MergeQueueEntry[];
|
|
108
|
+
/**
|
|
109
|
+
* Run a single merge queue cycle.
|
|
110
|
+
*
|
|
111
|
+
* Called by the daemon on each interval. Handles the state machine:
|
|
112
|
+
* - If paused, returns immediately
|
|
113
|
+
* - If a PR is in progress (rebased, waiting CI), checks status
|
|
114
|
+
* - If no PR in progress, picks next from queue and rebases
|
|
115
|
+
*/
|
|
116
|
+
export declare function runMergeQueueCycle(options: MergeQueueOptions, deps?: MergeQueueDeps): MergeQueueCycleResult;
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge Queue
|
|
3
|
+
*
|
|
4
|
+
* Sequential rebase-test-merge pipeline for parallel agent PRs.
|
|
5
|
+
*
|
|
6
|
+
* With strict branch protection (PR must be up-to-date with main), parallel
|
|
7
|
+
* agent PRs create a rebase treadmill. The merge queue processes green PRs
|
|
8
|
+
* one at a time: rebase on main → wait for CI → merge if green → eject if red.
|
|
9
|
+
*
|
|
10
|
+
* State machine per cycle:
|
|
11
|
+
* 1. If paused → skip
|
|
12
|
+
* 2. If a PR is currently being processed (rebased, waiting for CI):
|
|
13
|
+
* - CI green → merge, rebase siblings
|
|
14
|
+
* - CI failed → eject (label + comment)
|
|
15
|
+
* - CI pending → wait
|
|
16
|
+
* 3. If no current PR:
|
|
17
|
+
* - Build queue: open, non-draft PRs with green CI, ordered by priority then age
|
|
18
|
+
* - Pick first → rebase → set as current
|
|
19
|
+
* - If rebase fails (conflicts) → eject, try next
|
|
20
|
+
*/
|
|
21
|
+
import * as fs from 'node:fs';
|
|
22
|
+
import * as path from 'node:path';
|
|
23
|
+
import { listOpenPRs, getPRChecks, getPRByNumber, mergePR, } from '../pr/index.js';
|
|
24
|
+
import { rebaseSiblingPRs } from '../shipping/rebase.js';
|
|
25
|
+
import { GitHubProvider } from '../shipping/github.js';
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// Constants
|
|
28
|
+
// =============================================================================
|
|
29
|
+
const MERGE_QUEUE_STATE_FILE = 'merge-queue.json';
|
|
30
|
+
const MAX_EJECTED_HISTORY = 10;
|
|
31
|
+
const EJECTED_LABEL = 'merge-queue-ejected';
|
|
32
|
+
const EJECTED_COMMENT_PREFIX = 'Merge queue ejected this PR:';
|
|
33
|
+
const DEFAULT_PRIORITY = 3; // P3 — lowest
|
|
34
|
+
// =============================================================================
|
|
35
|
+
// State Management
|
|
36
|
+
// =============================================================================
|
|
37
|
+
export function getMergeQueueStatePath(hqPath) {
|
|
38
|
+
return path.join(hqPath, '.proletariat', MERGE_QUEUE_STATE_FILE);
|
|
39
|
+
}
|
|
40
|
+
export function getDefaultState() {
|
|
41
|
+
return {
|
|
42
|
+
paused: false,
|
|
43
|
+
pausedAt: null,
|
|
44
|
+
currentPR: null,
|
|
45
|
+
lastMergedPR: null,
|
|
46
|
+
lastCycleAt: null,
|
|
47
|
+
ejectedPRs: [],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export function getMergeQueueState(hqPath) {
|
|
51
|
+
const statePath = getMergeQueueStatePath(hqPath);
|
|
52
|
+
try {
|
|
53
|
+
const content = fs.readFileSync(statePath, 'utf-8');
|
|
54
|
+
return { ...getDefaultState(), ...JSON.parse(content) };
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return getDefaultState();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export function saveMergeQueueState(hqPath, state) {
|
|
61
|
+
const statePath = getMergeQueueStatePath(hqPath);
|
|
62
|
+
fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
63
|
+
}
|
|
64
|
+
// =============================================================================
|
|
65
|
+
// Pause / Resume
|
|
66
|
+
// =============================================================================
|
|
67
|
+
export function isMergeQueuePaused(hqPath) {
|
|
68
|
+
return getMergeQueueState(hqPath).paused;
|
|
69
|
+
}
|
|
70
|
+
export function pauseMergeQueue(hqPath) {
|
|
71
|
+
const state = getMergeQueueState(hqPath);
|
|
72
|
+
state.paused = true;
|
|
73
|
+
state.pausedAt = new Date().toISOString();
|
|
74
|
+
saveMergeQueueState(hqPath, state);
|
|
75
|
+
return state;
|
|
76
|
+
}
|
|
77
|
+
export function resumeMergeQueue(hqPath) {
|
|
78
|
+
const state = getMergeQueueState(hqPath);
|
|
79
|
+
state.paused = false;
|
|
80
|
+
state.pausedAt = null;
|
|
81
|
+
saveMergeQueueState(hqPath, state);
|
|
82
|
+
return state;
|
|
83
|
+
}
|
|
84
|
+
// =============================================================================
|
|
85
|
+
// Queue Building
|
|
86
|
+
// =============================================================================
|
|
87
|
+
/**
|
|
88
|
+
* Build the merge queue: open, non-draft PRs with all CI checks green,
|
|
89
|
+
* ordered by priority (P0 first) then by creation date (oldest first).
|
|
90
|
+
*/
|
|
91
|
+
export function buildMergeQueue(options, deps = { listOpenPRs, getPRChecks }) {
|
|
92
|
+
const { cwd, branchPriorities } = options;
|
|
93
|
+
const openPRs = deps.listOpenPRs(cwd);
|
|
94
|
+
const entries = [];
|
|
95
|
+
for (const pr of openPRs) {
|
|
96
|
+
// Skip drafts
|
|
97
|
+
if (pr.isDraft)
|
|
98
|
+
continue;
|
|
99
|
+
// Check CI
|
|
100
|
+
const checks = deps.getPRChecks(pr.number, cwd);
|
|
101
|
+
if (checks.length === 0)
|
|
102
|
+
continue; // No checks = not ready
|
|
103
|
+
if (!allChecksGreen(checks))
|
|
104
|
+
continue;
|
|
105
|
+
// Resolve priority from branch
|
|
106
|
+
const priority = branchPriorities?.get(pr.headBranch) ?? DEFAULT_PRIORITY;
|
|
107
|
+
const ticketId = extractTicketIdFromBranch(pr.headBranch);
|
|
108
|
+
entries.push({ pr, ticketId, priority });
|
|
109
|
+
}
|
|
110
|
+
// Sort: lower priority number first (P0 < P1 < P2 < P3), then oldest first
|
|
111
|
+
entries.sort((a, b) => {
|
|
112
|
+
if (a.priority !== b.priority)
|
|
113
|
+
return a.priority - b.priority;
|
|
114
|
+
return new Date(a.pr.createdAt).getTime() - new Date(b.pr.createdAt).getTime();
|
|
115
|
+
});
|
|
116
|
+
return entries;
|
|
117
|
+
}
|
|
118
|
+
// =============================================================================
|
|
119
|
+
// Main Cycle
|
|
120
|
+
// =============================================================================
|
|
121
|
+
const _defaultProvider = new GitHubProvider();
|
|
122
|
+
const defaultDeps = {
|
|
123
|
+
listOpenPRs,
|
|
124
|
+
getPRChecks,
|
|
125
|
+
getPRByNumber,
|
|
126
|
+
mergePR,
|
|
127
|
+
rebaseSiblingPRs,
|
|
128
|
+
updatePRBranch: (prNumber, cwd) => _defaultProvider.updatePRBranch(prNumber, cwd),
|
|
129
|
+
addLabel: (prNumber, label, cwd) => _defaultProvider.addLabel(prNumber, label, cwd),
|
|
130
|
+
addComment: (prNumber, body, cwd) => _defaultProvider.addComment(prNumber, body, cwd),
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Run a single merge queue cycle.
|
|
134
|
+
*
|
|
135
|
+
* Called by the daemon on each interval. Handles the state machine:
|
|
136
|
+
* - If paused, returns immediately
|
|
137
|
+
* - If a PR is in progress (rebased, waiting CI), checks status
|
|
138
|
+
* - If no PR in progress, picks next from queue and rebases
|
|
139
|
+
*/
|
|
140
|
+
export function runMergeQueueCycle(options, deps = defaultDeps) {
|
|
141
|
+
const { hqPath, cwd, log, mergeMethod = 'squash', deleteBranch = true, branchPriorities, } = options;
|
|
142
|
+
const state = getMergeQueueState(hqPath);
|
|
143
|
+
state.lastCycleAt = new Date().toISOString();
|
|
144
|
+
// Step 1: Check if paused
|
|
145
|
+
if (state.paused) {
|
|
146
|
+
saveMergeQueueState(hqPath, state);
|
|
147
|
+
log?.('Merge queue is paused');
|
|
148
|
+
return { action: 'paused', queueSize: 0 };
|
|
149
|
+
}
|
|
150
|
+
// Step 2: If a PR is currently being processed, check its status
|
|
151
|
+
if (state.currentPR) {
|
|
152
|
+
const result = processCurrentPR(state, { cwd, log, mergeMethod, deleteBranch }, deps);
|
|
153
|
+
saveMergeQueueState(hqPath, state);
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
// Step 3: No current PR — build queue and pick next
|
|
157
|
+
const queue = buildMergeQueue({ cwd, branchPriorities }, deps);
|
|
158
|
+
log?.(`Merge queue: ${queue.length} eligible PR(s)`);
|
|
159
|
+
if (queue.length === 0) {
|
|
160
|
+
saveMergeQueueState(hqPath, state);
|
|
161
|
+
return { action: 'none', queueSize: 0 };
|
|
162
|
+
}
|
|
163
|
+
// Try to rebase the top PR — if conflicts, eject and try next
|
|
164
|
+
for (const entry of queue) {
|
|
165
|
+
const rebaseResult = deps.updatePRBranch(entry.pr.number, cwd);
|
|
166
|
+
if (rebaseResult.success) {
|
|
167
|
+
state.currentPR = {
|
|
168
|
+
prNumber: entry.pr.number,
|
|
169
|
+
headBranch: entry.pr.headBranch,
|
|
170
|
+
baseBranch: entry.pr.baseBranch,
|
|
171
|
+
ticketId: entry.ticketId,
|
|
172
|
+
priority: entry.priority,
|
|
173
|
+
rebasedAt: new Date().toISOString(),
|
|
174
|
+
};
|
|
175
|
+
log?.(`Rebased PR #${entry.pr.number} (${entry.pr.headBranch}) — waiting for CI`);
|
|
176
|
+
saveMergeQueueState(hqPath, state);
|
|
177
|
+
return { action: 'rebase_started', prNumber: entry.pr.number, queueSize: queue.length };
|
|
178
|
+
}
|
|
179
|
+
// Rebase failed — eject this PR
|
|
180
|
+
const reason = rebaseResult.hasConflicts
|
|
181
|
+
? 'Merge conflicts during rebase'
|
|
182
|
+
: `Rebase failed: ${rebaseResult.error ?? 'unknown error'}`;
|
|
183
|
+
ejectPR(state, entry.pr.number, entry.pr.headBranch, reason, cwd, deps);
|
|
184
|
+
log?.(`Ejected PR #${entry.pr.number}: ${reason}`);
|
|
185
|
+
}
|
|
186
|
+
// All PRs in queue had conflicts
|
|
187
|
+
saveMergeQueueState(hqPath, state);
|
|
188
|
+
return { action: 'none', reason: 'All eligible PRs have conflicts', queueSize: queue.length };
|
|
189
|
+
}
|
|
190
|
+
// =============================================================================
|
|
191
|
+
// Process Current PR
|
|
192
|
+
// =============================================================================
|
|
193
|
+
function processCurrentPR(state, options, deps) {
|
|
194
|
+
const { cwd, log, mergeMethod, deleteBranch } = options;
|
|
195
|
+
const current = state.currentPR;
|
|
196
|
+
const queueSize = 0; // We don't rebuild the queue when checking current
|
|
197
|
+
// Verify PR is still open
|
|
198
|
+
const prInfo = deps.getPRByNumber(current.prNumber, cwd);
|
|
199
|
+
if (!prInfo || prInfo.state !== 'OPEN') {
|
|
200
|
+
const reason = prInfo?.state === 'MERGED'
|
|
201
|
+
? 'PR was merged externally'
|
|
202
|
+
: 'PR is no longer open';
|
|
203
|
+
log?.(`Current PR #${current.prNumber}: ${reason} — clearing`);
|
|
204
|
+
state.currentPR = null;
|
|
205
|
+
if (prInfo?.state === 'MERGED') {
|
|
206
|
+
state.lastMergedPR = {
|
|
207
|
+
prNumber: current.prNumber,
|
|
208
|
+
headBranch: current.headBranch,
|
|
209
|
+
mergedAt: new Date().toISOString(),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return { action: 'none', prNumber: current.prNumber, reason, queueSize };
|
|
213
|
+
}
|
|
214
|
+
// Check CI status
|
|
215
|
+
const checks = deps.getPRChecks(current.prNumber, cwd);
|
|
216
|
+
// No checks yet — still waiting
|
|
217
|
+
if (checks.length === 0) {
|
|
218
|
+
log?.(`PR #${current.prNumber}: no CI checks yet — waiting`);
|
|
219
|
+
return { action: 'waiting_ci', prNumber: current.prNumber, queueSize };
|
|
220
|
+
}
|
|
221
|
+
const failed = checks.filter(c => c.conclusion === 'FAILURE');
|
|
222
|
+
const pending = checks.filter(c => !c.conclusion || c.status === 'IN_PROGRESS' || c.status === 'QUEUED');
|
|
223
|
+
// CI still pending
|
|
224
|
+
if (pending.length > 0) {
|
|
225
|
+
log?.(`PR #${current.prNumber}: CI pending (${pending.length} check(s)) — waiting`);
|
|
226
|
+
return { action: 'waiting_ci', prNumber: current.prNumber, queueSize };
|
|
227
|
+
}
|
|
228
|
+
// CI failed — eject
|
|
229
|
+
if (failed.length > 0) {
|
|
230
|
+
const failedNames = failed.map(c => c.name).join(', ');
|
|
231
|
+
const reason = `CI failed after rebase: ${failedNames}`;
|
|
232
|
+
ejectPR(state, current.prNumber, current.headBranch, reason, cwd, deps);
|
|
233
|
+
state.currentPR = null;
|
|
234
|
+
log?.(`Ejected PR #${current.prNumber}: ${reason}`);
|
|
235
|
+
return { action: 'ejected', prNumber: current.prNumber, reason, queueSize };
|
|
236
|
+
}
|
|
237
|
+
// All CI green — merge!
|
|
238
|
+
log?.(`PR #${current.prNumber}: CI green — merging (${mergeMethod})`);
|
|
239
|
+
const mergeResult = deps.mergePR(current.prNumber, {
|
|
240
|
+
method: mergeMethod,
|
|
241
|
+
deleteBranch,
|
|
242
|
+
cwd,
|
|
243
|
+
});
|
|
244
|
+
if (!mergeResult.success) {
|
|
245
|
+
// Check if it was merged by someone else
|
|
246
|
+
const recheck = deps.getPRByNumber(current.prNumber, cwd);
|
|
247
|
+
if (recheck?.state === 'MERGED') {
|
|
248
|
+
log?.(`PR #${current.prNumber}: was merged externally`);
|
|
249
|
+
state.currentPR = null;
|
|
250
|
+
state.lastMergedPR = {
|
|
251
|
+
prNumber: current.prNumber,
|
|
252
|
+
headBranch: current.headBranch,
|
|
253
|
+
mergedAt: new Date().toISOString(),
|
|
254
|
+
};
|
|
255
|
+
return { action: 'merged', prNumber: current.prNumber, reason: 'Merged externally', queueSize };
|
|
256
|
+
}
|
|
257
|
+
const reason = `Merge failed: ${mergeResult.error}`;
|
|
258
|
+
ejectPR(state, current.prNumber, current.headBranch, reason, cwd, deps);
|
|
259
|
+
state.currentPR = null;
|
|
260
|
+
log?.(`Ejected PR #${current.prNumber}: ${reason}`);
|
|
261
|
+
return { action: 'ejected', prNumber: current.prNumber, reason, queueSize };
|
|
262
|
+
}
|
|
263
|
+
// Merge succeeded!
|
|
264
|
+
log?.(`Merged PR #${current.prNumber} (${current.headBranch})`);
|
|
265
|
+
state.currentPR = null;
|
|
266
|
+
state.lastMergedPR = {
|
|
267
|
+
prNumber: current.prNumber,
|
|
268
|
+
headBranch: current.headBranch,
|
|
269
|
+
mergedAt: new Date().toISOString(),
|
|
270
|
+
};
|
|
271
|
+
// Rebase sibling PRs
|
|
272
|
+
log?.('Rebasing sibling PRs...');
|
|
273
|
+
const siblingResult = deps.rebaseSiblingPRs({
|
|
274
|
+
excludePRNumber: current.prNumber,
|
|
275
|
+
cwd,
|
|
276
|
+
onProgress: log,
|
|
277
|
+
labelConflicts: true,
|
|
278
|
+
commentOnConflicts: true,
|
|
279
|
+
});
|
|
280
|
+
log?.(`Siblings: ${siblingResult.succeeded.length} rebased, ${siblingResult.failed.length} failed, ${siblingResult.skipped.length} skipped`);
|
|
281
|
+
return { action: 'merged', prNumber: current.prNumber, queueSize };
|
|
282
|
+
}
|
|
283
|
+
// =============================================================================
|
|
284
|
+
// Eject PR
|
|
285
|
+
// =============================================================================
|
|
286
|
+
function ejectPR(state, prNumber, headBranch, reason, cwd, deps) {
|
|
287
|
+
// Record ejection
|
|
288
|
+
state.ejectedPRs.unshift({
|
|
289
|
+
prNumber,
|
|
290
|
+
headBranch,
|
|
291
|
+
reason,
|
|
292
|
+
ejectedAt: new Date().toISOString(),
|
|
293
|
+
});
|
|
294
|
+
// Keep only the last N ejected PRs
|
|
295
|
+
if (state.ejectedPRs.length > MAX_EJECTED_HISTORY) {
|
|
296
|
+
state.ejectedPRs = state.ejectedPRs.slice(0, MAX_EJECTED_HISTORY);
|
|
297
|
+
}
|
|
298
|
+
// Label and comment on the PR (best effort)
|
|
299
|
+
try {
|
|
300
|
+
deps.addLabel(prNumber, EJECTED_LABEL, cwd);
|
|
301
|
+
deps.addComment(prNumber, `${EJECTED_COMMENT_PREFIX} ${reason}`, cwd);
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
// Best effort — don't fail the cycle
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// =============================================================================
|
|
308
|
+
// Helpers
|
|
309
|
+
// =============================================================================
|
|
310
|
+
function allChecksGreen(checks) {
|
|
311
|
+
return checks.every(c => c.conclusion === 'SUCCESS' || c.conclusion === 'NEUTRAL' || c.conclusion === 'SKIPPED');
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Extract a ticket ID from a branch name.
|
|
315
|
+
* Supports patterns like: PRLT-1173/feat/... or TKT-007/...
|
|
316
|
+
*/
|
|
317
|
+
function extractTicketIdFromBranch(branch) {
|
|
318
|
+
const match = branch.match(/^([A-Z]+-\d+)\//);
|
|
319
|
+
return match?.[1] ?? null;
|
|
320
|
+
}
|
|
321
|
+
//# sourceMappingURL=merge-queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge-queue.js","sourceRoot":"","sources":["../../../src/lib/sync/merge-queue.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Board Sync Reconciler
|
|
3
|
+
*
|
|
4
|
+
* Core reconciliation logic that detects drift between GitHub PR state
|
|
5
|
+
* and ticket provider state, then produces corrective actions.
|
|
6
|
+
*
|
|
7
|
+
* Provider-agnostic: works with any ticket provider (Linear, PMO, etc.)
|
|
8
|
+
* and any git host that exposes PR info.
|
|
9
|
+
*
|
|
10
|
+
* Reconciliation rules:
|
|
11
|
+
* 1. Merged PR + ticket not Done → move to Done
|
|
12
|
+
* 2. Green CI + ticket In Progress → move to Review
|
|
13
|
+
* 3. Ticket In Progress + no active agent → flag as stale
|
|
14
|
+
* 4. Ticket In Review + PR closed (not merged) → move to Backlog
|
|
15
|
+
*/
|
|
16
|
+
import type { PRInfo, PRCheck } from '../pr/index.js';
|
|
17
|
+
import type { Ticket } from '../pmo/types.js';
|
|
18
|
+
export type ReconcileActionType = 'move_to_done' | 'move_to_review' | 'flag_stale' | 'move_to_backlog';
|
|
19
|
+
export interface ReconcileAction {
|
|
20
|
+
type: ReconcileActionType;
|
|
21
|
+
ticketId: string;
|
|
22
|
+
ticketTitle: string;
|
|
23
|
+
reason: string;
|
|
24
|
+
/** Target status name to move to (for move actions) */
|
|
25
|
+
targetStatus?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface ReconcileContext {
|
|
28
|
+
ticket: Ticket;
|
|
29
|
+
pr: PRInfo | null;
|
|
30
|
+
checks: PRCheck[];
|
|
31
|
+
hasActiveExecution: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface ReconcileResult {
|
|
34
|
+
actions: ReconcileAction[];
|
|
35
|
+
checked: number;
|
|
36
|
+
errors: Array<{
|
|
37
|
+
ticketId: string;
|
|
38
|
+
error: string;
|
|
39
|
+
}>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Determine reconciliation actions for a single ticket.
|
|
43
|
+
*/
|
|
44
|
+
export declare function reconcileTicket(ctx: ReconcileContext): ReconcileAction | null;
|