@generacy-ai/orchestrator 0.0.0-preview-20260304013206
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/LICENSE +191 -0
- package/dist/auth/api-key.d.ts +63 -0
- package/dist/auth/api-key.d.ts.map +1 -0
- package/dist/auth/api-key.js +102 -0
- package/dist/auth/api-key.js.map +1 -0
- package/dist/auth/github-oauth.d.ts +29 -0
- package/dist/auth/github-oauth.d.ts.map +1 -0
- package/dist/auth/github-oauth.js +136 -0
- package/dist/auth/github-oauth.js.map +1 -0
- package/dist/auth/index.d.ts +5 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +9 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/jwt.d.ts +34 -0
- package/dist/auth/jwt.d.ts.map +1 -0
- package/dist/auth/jwt.js +74 -0
- package/dist/auth/jwt.js.map +1 -0
- package/dist/auth/middleware.d.ts +43 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +101 -0
- package/dist/auth/middleware.js.map +1 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +5 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +21 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +295 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/schema.d.ts +733 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +174 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/error-handler.d.ts +30 -0
- package/dist/middleware/error-handler.d.ts.map +1 -0
- package/dist/middleware/error-handler.js +173 -0
- package/dist/middleware/error-handler.js.map +1 -0
- package/dist/middleware/index.d.ts +4 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +4 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/rate-limit.d.ts +32 -0
- package/dist/middleware/rate-limit.d.ts.map +1 -0
- package/dist/middleware/rate-limit.js +92 -0
- package/dist/middleware/rate-limit.js.map +1 -0
- package/dist/middleware/request-logger.d.ts +31 -0
- package/dist/middleware/request-logger.d.ts.map +1 -0
- package/dist/middleware/request-logger.js +89 -0
- package/dist/middleware/request-logger.js.map +1 -0
- package/dist/routes/agents.d.ts +7 -0
- package/dist/routes/agents.d.ts.map +1 -0
- package/dist/routes/agents.js +78 -0
- package/dist/routes/agents.js.map +1 -0
- package/dist/routes/dispatch.d.ts +8 -0
- package/dist/routes/dispatch.d.ts.map +1 -0
- package/dist/routes/dispatch.js +46 -0
- package/dist/routes/dispatch.js.map +1 -0
- package/dist/routes/events.d.ts +14 -0
- package/dist/routes/events.d.ts.map +1 -0
- package/dist/routes/events.js +240 -0
- package/dist/routes/events.js.map +1 -0
- package/dist/routes/health.d.ts +14 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +132 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/index.d.ts +36 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +34 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/integrations.d.ts +34 -0
- package/dist/routes/integrations.d.ts.map +1 -0
- package/dist/routes/integrations.js +81 -0
- package/dist/routes/integrations.js.map +1 -0
- package/dist/routes/metrics.d.ts +31 -0
- package/dist/routes/metrics.d.ts.map +1 -0
- package/dist/routes/metrics.js +157 -0
- package/dist/routes/metrics.js.map +1 -0
- package/dist/routes/pr-webhooks.d.ts +15 -0
- package/dist/routes/pr-webhooks.d.ts.map +1 -0
- package/dist/routes/pr-webhooks.js +114 -0
- package/dist/routes/pr-webhooks.js.map +1 -0
- package/dist/routes/queue.d.ts +7 -0
- package/dist/routes/queue.d.ts.map +1 -0
- package/dist/routes/queue.js +94 -0
- package/dist/routes/queue.js.map +1 -0
- package/dist/routes/webhooks.d.ts +15 -0
- package/dist/routes/webhooks.d.ts.map +1 -0
- package/dist/routes/webhooks.js +117 -0
- package/dist/routes/webhooks.js.map +1 -0
- package/dist/routes/workflows.d.ts +7 -0
- package/dist/routes/workflows.d.ts.map +1 -0
- package/dist/routes/workflows.js +138 -0
- package/dist/routes/workflows.js.map +1 -0
- package/dist/server.d.ts +35 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +264 -0
- package/dist/server.js.map +1 -0
- package/dist/services/agent-registry.d.ts +89 -0
- package/dist/services/agent-registry.d.ts.map +1 -0
- package/dist/services/agent-registry.js +173 -0
- package/dist/services/agent-registry.js.map +1 -0
- package/dist/services/epic-completion-monitor-service.d.ts +69 -0
- package/dist/services/epic-completion-monitor-service.d.ts.map +1 -0
- package/dist/services/epic-completion-monitor-service.js +200 -0
- package/dist/services/epic-completion-monitor-service.js.map +1 -0
- package/dist/services/identity.d.ts +41 -0
- package/dist/services/identity.d.ts.map +1 -0
- package/dist/services/identity.js +91 -0
- package/dist/services/identity.js.map +1 -0
- package/dist/services/index.d.ts +13 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +13 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/label-monitor-service.d.ts +95 -0
- package/dist/services/label-monitor-service.d.ts.map +1 -0
- package/dist/services/label-monitor-service.js +414 -0
- package/dist/services/label-monitor-service.js.map +1 -0
- package/dist/services/label-sync-service.d.ts +76 -0
- package/dist/services/label-sync-service.d.ts.map +1 -0
- package/dist/services/label-sync-service.js +108 -0
- package/dist/services/label-sync-service.js.map +1 -0
- package/dist/services/phase-tracker-service.d.ts +32 -0
- package/dist/services/phase-tracker-service.d.ts.map +1 -0
- package/dist/services/phase-tracker-service.js +82 -0
- package/dist/services/phase-tracker-service.js.map +1 -0
- package/dist/services/pr-feedback-monitor-service.d.ts +86 -0
- package/dist/services/pr-feedback-monitor-service.d.ts.map +1 -0
- package/dist/services/pr-feedback-monitor-service.js +423 -0
- package/dist/services/pr-feedback-monitor-service.js.map +1 -0
- package/dist/services/queue-service.d.ts +62 -0
- package/dist/services/queue-service.d.ts.map +1 -0
- package/dist/services/queue-service.js +147 -0
- package/dist/services/queue-service.js.map +1 -0
- package/dist/services/redis-queue-adapter.d.ts +35 -0
- package/dist/services/redis-queue-adapter.d.ts.map +1 -0
- package/dist/services/redis-queue-adapter.js +218 -0
- package/dist/services/redis-queue-adapter.js.map +1 -0
- package/dist/services/smee-receiver.d.ts +87 -0
- package/dist/services/smee-receiver.d.ts.map +1 -0
- package/dist/services/smee-receiver.js +225 -0
- package/dist/services/smee-receiver.js.map +1 -0
- package/dist/services/webhook-setup-service.d.ts +253 -0
- package/dist/services/webhook-setup-service.d.ts.map +1 -0
- package/dist/services/webhook-setup-service.js +369 -0
- package/dist/services/webhook-setup-service.js.map +1 -0
- package/dist/services/worker-dispatcher.d.ts +57 -0
- package/dist/services/worker-dispatcher.d.ts.map +1 -0
- package/dist/services/worker-dispatcher.js +211 -0
- package/dist/services/worker-dispatcher.js.map +1 -0
- package/dist/services/workflow-service.d.ts +65 -0
- package/dist/services/workflow-service.d.ts.map +1 -0
- package/dist/services/workflow-service.js +164 -0
- package/dist/services/workflow-service.js.map +1 -0
- package/dist/sse/events.d.ts +50 -0
- package/dist/sse/events.d.ts.map +1 -0
- package/dist/sse/events.js +152 -0
- package/dist/sse/events.js.map +1 -0
- package/dist/sse/index.d.ts +4 -0
- package/dist/sse/index.d.ts.map +1 -0
- package/dist/sse/index.js +7 -0
- package/dist/sse/index.js.map +1 -0
- package/dist/sse/stream.d.ts +109 -0
- package/dist/sse/stream.d.ts.map +1 -0
- package/dist/sse/stream.js +239 -0
- package/dist/sse/stream.js.map +1 -0
- package/dist/sse/subscriptions.d.ts +91 -0
- package/dist/sse/subscriptions.d.ts.map +1 -0
- package/dist/sse/subscriptions.js +299 -0
- package/dist/sse/subscriptions.js.map +1 -0
- package/dist/types/api.d.ts +651 -0
- package/dist/types/api.d.ts.map +1 -0
- package/dist/types/api.js +169 -0
- package/dist/types/api.js.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +27 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/monitor.d.ts +243 -0
- package/dist/types/monitor.d.ts.map +1 -0
- package/dist/types/monitor.js +2 -0
- package/dist/types/monitor.js.map +1 -0
- package/dist/types/problem-details.d.ts +63 -0
- package/dist/types/problem-details.d.ts.map +1 -0
- package/dist/types/problem-details.js +37 -0
- package/dist/types/problem-details.js.map +1 -0
- package/dist/types/sse.d.ts +224 -0
- package/dist/types/sse.d.ts.map +1 -0
- package/dist/types/sse.js +103 -0
- package/dist/types/sse.js.map +1 -0
- package/dist/types/webhook.d.ts +59 -0
- package/dist/types/webhook.d.ts.map +1 -0
- package/dist/types/webhook.js +2 -0
- package/dist/types/webhook.js.map +1 -0
- package/dist/utils/correlation.d.ts +30 -0
- package/dist/utils/correlation.d.ts.map +1 -0
- package/dist/utils/correlation.js +34 -0
- package/dist/utils/correlation.js.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/shutdown.d.ts +28 -0
- package/dist/utils/shutdown.d.ts.map +1 -0
- package/dist/utils/shutdown.js +86 -0
- package/dist/utils/shutdown.js.map +1 -0
- package/dist/worker/claude-cli-worker.d.ts +61 -0
- package/dist/worker/claude-cli-worker.d.ts.map +1 -0
- package/dist/worker/claude-cli-worker.js +330 -0
- package/dist/worker/claude-cli-worker.js.map +1 -0
- package/dist/worker/cli-spawner.d.ts +42 -0
- package/dist/worker/cli-spawner.d.ts.map +1 -0
- package/dist/worker/cli-spawner.js +183 -0
- package/dist/worker/cli-spawner.js.map +1 -0
- package/dist/worker/config.d.ts +76 -0
- package/dist/worker/config.d.ts.map +1 -0
- package/dist/worker/config.js +39 -0
- package/dist/worker/config.js.map +1 -0
- package/dist/worker/epic-post-tasks.d.ts +73 -0
- package/dist/worker/epic-post-tasks.d.ts.map +1 -0
- package/dist/worker/epic-post-tasks.js +266 -0
- package/dist/worker/epic-post-tasks.js.map +1 -0
- package/dist/worker/gate-checker.d.ts +22 -0
- package/dist/worker/gate-checker.d.ts.map +1 -0
- package/dist/worker/gate-checker.js +41 -0
- package/dist/worker/gate-checker.js.map +1 -0
- package/dist/worker/index.d.ts +13 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/index.js +19 -0
- package/dist/worker/index.js.map +1 -0
- package/dist/worker/label-manager.d.ts +89 -0
- package/dist/worker/label-manager.d.ts.map +1 -0
- package/dist/worker/label-manager.js +200 -0
- package/dist/worker/label-manager.js.map +1 -0
- package/dist/worker/output-capture.d.ts +58 -0
- package/dist/worker/output-capture.d.ts.map +1 -0
- package/dist/worker/output-capture.js +153 -0
- package/dist/worker/output-capture.js.map +1 -0
- package/dist/worker/phase-loop.d.ts +64 -0
- package/dist/worker/phase-loop.d.ts.map +1 -0
- package/dist/worker/phase-loop.js +185 -0
- package/dist/worker/phase-loop.js.map +1 -0
- package/dist/worker/phase-resolver.d.ts +62 -0
- package/dist/worker/phase-resolver.d.ts.map +1 -0
- package/dist/worker/phase-resolver.js +144 -0
- package/dist/worker/phase-resolver.js.map +1 -0
- package/dist/worker/pr-feedback-handler.d.ts +105 -0
- package/dist/worker/pr-feedback-handler.d.ts.map +1 -0
- package/dist/worker/pr-feedback-handler.js +391 -0
- package/dist/worker/pr-feedback-handler.js.map +1 -0
- package/dist/worker/pr-linker.d.ts +69 -0
- package/dist/worker/pr-linker.d.ts.map +1 -0
- package/dist/worker/pr-linker.js +108 -0
- package/dist/worker/pr-linker.js.map +1 -0
- package/dist/worker/pr-manager.d.ts +61 -0
- package/dist/worker/pr-manager.d.ts.map +1 -0
- package/dist/worker/pr-manager.js +140 -0
- package/dist/worker/pr-manager.js.map +1 -0
- package/dist/worker/repo-checkout.d.ts +63 -0
- package/dist/worker/repo-checkout.d.ts.map +1 -0
- package/dist/worker/repo-checkout.js +160 -0
- package/dist/worker/repo-checkout.js.map +1 -0
- package/dist/worker/stage-comment-manager.d.ts +34 -0
- package/dist/worker/stage-comment-manager.d.ts.map +1 -0
- package/dist/worker/stage-comment-manager.js +114 -0
- package/dist/worker/stage-comment-manager.js.map +1 -0
- package/dist/worker/types.d.ts +192 -0
- package/dist/worker/types.d.ts.map +1 -0
- package/dist/worker/types.js +53 -0
- package/dist/worker/types.js.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import { WORKFLOW_LABELS } from '@generacy-ai/workflow-engine';
|
|
2
|
+
import { filterByAssignee } from './identity.js';
|
|
3
|
+
/**
|
|
4
|
+
* Known process:* and completed:* label names derived from WORKFLOW_LABELS.
|
|
5
|
+
* Using these avoids a GraphQL listLabels call on every poll cycle.
|
|
6
|
+
*/
|
|
7
|
+
const KNOWN_PROCESS_LABELS = WORKFLOW_LABELS
|
|
8
|
+
.filter(l => l.name.startsWith('process:'))
|
|
9
|
+
.map(l => l.name);
|
|
10
|
+
const KNOWN_COMPLETED_LABELS = WORKFLOW_LABELS
|
|
11
|
+
.filter(l => l.name.startsWith('completed:'))
|
|
12
|
+
.map(l => l.name);
|
|
13
|
+
const PROCESS_LABEL_PREFIX = 'process:';
|
|
14
|
+
const COMPLETED_LABEL_PREFIX = 'completed:';
|
|
15
|
+
const WAITING_FOR_LABEL_PREFIX = 'waiting-for:';
|
|
16
|
+
const AGENT_IN_PROGRESS_LABEL = 'agent:in-progress';
|
|
17
|
+
const MIN_POLL_INTERVAL_MS = 10000;
|
|
18
|
+
const ADAPTIVE_DIVISOR = 3;
|
|
19
|
+
/**
|
|
20
|
+
* Label monitor service that watches repositories for trigger labels
|
|
21
|
+
* using a hybrid webhook + polling approach.
|
|
22
|
+
*/
|
|
23
|
+
export class LabelMonitorService {
|
|
24
|
+
logger;
|
|
25
|
+
createClient;
|
|
26
|
+
phaseTracker;
|
|
27
|
+
queueAdapter;
|
|
28
|
+
options;
|
|
29
|
+
clusterGithubUsername;
|
|
30
|
+
abortController = null;
|
|
31
|
+
pollCycleCount = 0;
|
|
32
|
+
/**
|
|
33
|
+
* How often to check completed:* labels (every Nth cycle).
|
|
34
|
+
* Process:* labels are checked every cycle. Completed:* labels
|
|
35
|
+
* are for resume detection and can be checked less frequently.
|
|
36
|
+
*/
|
|
37
|
+
static COMPLETED_CHECK_INTERVAL = 3;
|
|
38
|
+
state;
|
|
39
|
+
constructor(logger, createClient, phaseTracker, queueAdapter, config, repositories, clusterGithubUsername) {
|
|
40
|
+
this.logger = logger;
|
|
41
|
+
this.createClient = createClient;
|
|
42
|
+
this.phaseTracker = phaseTracker;
|
|
43
|
+
this.queueAdapter = queueAdapter;
|
|
44
|
+
this.clusterGithubUsername = clusterGithubUsername;
|
|
45
|
+
this.options = {
|
|
46
|
+
repositories,
|
|
47
|
+
pollIntervalMs: config.pollIntervalMs,
|
|
48
|
+
adaptivePolling: config.adaptivePolling,
|
|
49
|
+
maxConcurrentPolls: config.maxConcurrentPolls,
|
|
50
|
+
};
|
|
51
|
+
this.state = {
|
|
52
|
+
isPolling: false,
|
|
53
|
+
webhookHealthy: true,
|
|
54
|
+
lastWebhookEvent: null,
|
|
55
|
+
currentPollIntervalMs: config.pollIntervalMs,
|
|
56
|
+
basePollIntervalMs: config.pollIntervalMs,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// ==========================================================================
|
|
60
|
+
// Label Parsing
|
|
61
|
+
// ==========================================================================
|
|
62
|
+
/**
|
|
63
|
+
* Parse a label event from a label name, determining if it's a process
|
|
64
|
+
* trigger or a resume event.
|
|
65
|
+
*/
|
|
66
|
+
parseLabelEvent(labelName, owner, repo, issueNumber, issueLabels, source) {
|
|
67
|
+
// Check for process:* trigger labels
|
|
68
|
+
if (labelName.startsWith(PROCESS_LABEL_PREFIX)) {
|
|
69
|
+
const workflowName = labelName.slice(PROCESS_LABEL_PREFIX.length);
|
|
70
|
+
if (!workflowName)
|
|
71
|
+
return null;
|
|
72
|
+
return {
|
|
73
|
+
type: 'process',
|
|
74
|
+
owner,
|
|
75
|
+
repo,
|
|
76
|
+
issueNumber,
|
|
77
|
+
labelName,
|
|
78
|
+
parsedName: workflowName,
|
|
79
|
+
source,
|
|
80
|
+
issueLabels,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
// Check for completed:* labels (resume detection)
|
|
84
|
+
if (labelName.startsWith(COMPLETED_LABEL_PREFIX)) {
|
|
85
|
+
const phaseName = labelName.slice(COMPLETED_LABEL_PREFIX.length);
|
|
86
|
+
if (!phaseName)
|
|
87
|
+
return null;
|
|
88
|
+
// Check for matching waiting-for:* label
|
|
89
|
+
const waitingLabel = `${WAITING_FOR_LABEL_PREFIX}${phaseName}`;
|
|
90
|
+
if (issueLabels.includes(waitingLabel)) {
|
|
91
|
+
return {
|
|
92
|
+
type: 'resume',
|
|
93
|
+
owner,
|
|
94
|
+
repo,
|
|
95
|
+
issueNumber,
|
|
96
|
+
labelName,
|
|
97
|
+
parsedName: phaseName,
|
|
98
|
+
source,
|
|
99
|
+
issueLabels,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
this.logger.info({ labelName, owner, repo, issueNumber, expectedWaitingLabel: waitingLabel, source }, 'completed:* label seen without matching waiting-for:* label');
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
// ==========================================================================
|
|
107
|
+
// Webhook Verification
|
|
108
|
+
// ==========================================================================
|
|
109
|
+
/**
|
|
110
|
+
* Re-fetch issue labels from GitHub and retry resume detection.
|
|
111
|
+
* Called when a webhook delivers a completed:* label but the payload's
|
|
112
|
+
* issueLabels didn't contain a matching waiting-for:* label (stale payload).
|
|
113
|
+
*/
|
|
114
|
+
async verifyAndProcessCompletedLabel(owner, repo, issueNumber, labelName) {
|
|
115
|
+
this.logger.info({ owner, repo, issueNumber, labelName }, 'Re-fetching issue labels for completed:* verification');
|
|
116
|
+
let freshLabels;
|
|
117
|
+
try {
|
|
118
|
+
const client = this.createClient();
|
|
119
|
+
const issue = await client.getIssue(owner, repo, issueNumber);
|
|
120
|
+
freshLabels = issue.labels.map(l => typeof l === 'string' ? l : l.name);
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
this.logger.error({ err: String(error), owner, repo, issueNumber, labelName }, 'Failed to re-fetch issue labels for completed:* verification');
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
const event = this.parseLabelEvent(labelName, owner, repo, issueNumber, freshLabels, 'webhook');
|
|
127
|
+
if (!event) {
|
|
128
|
+
this.logger.info({ owner, repo, issueNumber, labelName }, 'Re-fetch confirmed: no matching waiting-for:* label on issue');
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
this.logger.info({ owner, repo, issueNumber, labelName, type: event.type }, 'Re-fetch found matching waiting-for:* label, processing resume event');
|
|
132
|
+
return this.processLabelEvent(event);
|
|
133
|
+
}
|
|
134
|
+
// ==========================================================================
|
|
135
|
+
// Workflow Resolution
|
|
136
|
+
// ==========================================================================
|
|
137
|
+
/**
|
|
138
|
+
* Resolve workflow name from a workflow:* label on the issue.
|
|
139
|
+
* Falls back to 'speckit-feature' for backward compatibility with
|
|
140
|
+
* pre-existing issues that lack a workflow: label.
|
|
141
|
+
*/
|
|
142
|
+
resolveWorkflowFromLabels(issueLabels) {
|
|
143
|
+
const WORKFLOW_LABEL_PREFIX = 'workflow:';
|
|
144
|
+
const workflowLabel = issueLabels.find(l => l.startsWith(WORKFLOW_LABEL_PREFIX));
|
|
145
|
+
if (workflowLabel) {
|
|
146
|
+
return workflowLabel.slice(WORKFLOW_LABEL_PREFIX.length);
|
|
147
|
+
}
|
|
148
|
+
return 'speckit-feature';
|
|
149
|
+
}
|
|
150
|
+
// ==========================================================================
|
|
151
|
+
// Event Processing
|
|
152
|
+
// ==========================================================================
|
|
153
|
+
/**
|
|
154
|
+
* Process a label event: check dedup, enqueue, manage labels.
|
|
155
|
+
* Shared by both webhook and polling paths.
|
|
156
|
+
*/
|
|
157
|
+
async processLabelEvent(event) {
|
|
158
|
+
const { type, owner, repo, issueNumber, parsedName, source } = event;
|
|
159
|
+
this.logger.info({ type, owner, repo, issueNumber, parsedName, source }, `Processing ${type} label event`);
|
|
160
|
+
// Deduplication: prevents webhook+poll race from double-processing.
|
|
161
|
+
// For 'process' events, clear any existing dedup key first so the issue
|
|
162
|
+
// can be re-queued after a failure or completion. The label removal after
|
|
163
|
+
// processing prevents the poll from re-detecting the same label.
|
|
164
|
+
const dedupPhase = type === 'process' ? parsedName : `resume:${parsedName}`;
|
|
165
|
+
if (type === 'process') {
|
|
166
|
+
await this.phaseTracker.clear(owner, repo, issueNumber, dedupPhase);
|
|
167
|
+
}
|
|
168
|
+
const isDuplicate = await this.phaseTracker.isDuplicate(owner, repo, issueNumber, dedupPhase);
|
|
169
|
+
if (isDuplicate) {
|
|
170
|
+
this.logger.info({ owner, repo, issueNumber, phase: dedupPhase }, 'Skipping duplicate event');
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
// Resolve workflow name: for process events, use parsedName directly;
|
|
174
|
+
// for resume events, read the workflow:* label from the issue.
|
|
175
|
+
const workflowName = type === 'resume'
|
|
176
|
+
? this.resolveWorkflowFromLabels(event.issueLabels)
|
|
177
|
+
: parsedName;
|
|
178
|
+
if (type === 'resume' && !event.issueLabels.some(l => l.startsWith('workflow:'))) {
|
|
179
|
+
this.logger.warn({ owner, repo, issueNumber, defaultedTo: 'speckit-feature' }, 'No workflow: label found on issue, defaulting to speckit-feature');
|
|
180
|
+
}
|
|
181
|
+
// Build queue item
|
|
182
|
+
const queueItem = {
|
|
183
|
+
owner,
|
|
184
|
+
repo,
|
|
185
|
+
issueNumber,
|
|
186
|
+
workflowName,
|
|
187
|
+
command: type === 'process' ? 'process' : 'continue',
|
|
188
|
+
priority: Date.now(),
|
|
189
|
+
enqueuedAt: new Date().toISOString(),
|
|
190
|
+
};
|
|
191
|
+
// Enqueue
|
|
192
|
+
await this.queueAdapter.enqueue(queueItem);
|
|
193
|
+
this.logger.info({ owner, repo, issueNumber, command: queueItem.command, workflowName }, 'Issue enqueued');
|
|
194
|
+
// Mark as processed for dedup
|
|
195
|
+
await this.phaseTracker.markProcessed(owner, repo, issueNumber, dedupPhase);
|
|
196
|
+
// Manage labels via GitHubClient
|
|
197
|
+
const client = this.createClient();
|
|
198
|
+
if (type === 'process') {
|
|
199
|
+
// Remove trigger label, agent:error, and all completed:* labels from previous runs.
|
|
200
|
+
// Without clearing completed:* labels, requeued issues skip already-labeled phases
|
|
201
|
+
// even if the prior run failed mid-implementation.
|
|
202
|
+
try {
|
|
203
|
+
const issue = await client.getIssue(owner, repo, issueNumber);
|
|
204
|
+
const completedLabels = issue.labels
|
|
205
|
+
.map(l => typeof l === 'string' ? l : l.name)
|
|
206
|
+
.filter(name => name.startsWith(COMPLETED_LABEL_PREFIX));
|
|
207
|
+
const labelsToRemove = [event.labelName, 'agent:error', ...completedLabels];
|
|
208
|
+
await client.removeLabels(owner, repo, issueNumber, labelsToRemove);
|
|
209
|
+
await client.addLabels(owner, repo, issueNumber, [
|
|
210
|
+
AGENT_IN_PROGRESS_LABEL,
|
|
211
|
+
`workflow:${parsedName}`,
|
|
212
|
+
]);
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
this.logger.warn({ err: error, owner, repo, issueNumber }, 'Failed to update labels after process enqueue');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// Note: waiting-for:* label removal for resume events is handled by the
|
|
219
|
+
// worker (labelManager.onResumeStart) to avoid a race condition where
|
|
220
|
+
// the label is removed before the worker reads it for phase resolution.
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
// ==========================================================================
|
|
224
|
+
// Polling
|
|
225
|
+
// ==========================================================================
|
|
226
|
+
/**
|
|
227
|
+
* Start the polling loop.
|
|
228
|
+
*/
|
|
229
|
+
async startPolling() {
|
|
230
|
+
if (this.state.isPolling) {
|
|
231
|
+
this.logger.warn('Polling already running');
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const ac = new AbortController();
|
|
235
|
+
this.abortController = ac;
|
|
236
|
+
this.state.isPolling = true;
|
|
237
|
+
this.logger.info({ intervalMs: this.state.currentPollIntervalMs, repos: this.options.repositories.length }, 'Starting label monitor polling');
|
|
238
|
+
while (!ac.signal.aborted) {
|
|
239
|
+
try {
|
|
240
|
+
await this.poll();
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
this.logger.error({ err: error }, 'Error during poll cycle');
|
|
244
|
+
}
|
|
245
|
+
// Update adaptive polling before sleeping
|
|
246
|
+
if (this.options.adaptivePolling) {
|
|
247
|
+
this.updateAdaptivePolling();
|
|
248
|
+
}
|
|
249
|
+
await this.sleep(this.state.currentPollIntervalMs, ac.signal);
|
|
250
|
+
}
|
|
251
|
+
this.state.isPolling = false;
|
|
252
|
+
this.logger.info('Polling loop stopped');
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Stop the polling loop.
|
|
256
|
+
*/
|
|
257
|
+
stopPolling() {
|
|
258
|
+
if (this.abortController) {
|
|
259
|
+
this.abortController.abort();
|
|
260
|
+
this.abortController = null;
|
|
261
|
+
this.logger.info('Polling stop requested');
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Run a single poll cycle across all watched repositories.
|
|
266
|
+
* Process:* labels are checked every cycle (2 labels × N repos).
|
|
267
|
+
* Completed:* labels for resume detection are checked every Nth cycle.
|
|
268
|
+
*/
|
|
269
|
+
async poll() {
|
|
270
|
+
const repos = this.options.repositories;
|
|
271
|
+
if (repos.length === 0)
|
|
272
|
+
return;
|
|
273
|
+
this.pollCycleCount++;
|
|
274
|
+
const checkCompleted = this.pollCycleCount % LabelMonitorService.COMPLETED_CHECK_INTERVAL === 0;
|
|
275
|
+
// Use semaphore pattern for concurrency limiting
|
|
276
|
+
const semaphore = new Semaphore(this.options.maxConcurrentPolls);
|
|
277
|
+
const pollTasks = repos.map(({ owner, repo }) => semaphore.acquire().then(async (release) => {
|
|
278
|
+
try {
|
|
279
|
+
await this.pollRepo(owner, repo, checkCompleted);
|
|
280
|
+
}
|
|
281
|
+
finally {
|
|
282
|
+
release();
|
|
283
|
+
}
|
|
284
|
+
}));
|
|
285
|
+
await Promise.allSettled(pollTasks);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Poll a single repository for trigger labels.
|
|
289
|
+
* Uses known label names from WORKFLOW_LABELS to avoid GraphQL listLabels calls.
|
|
290
|
+
* listIssuesWithLabel uses the REST API (separate rate limit bucket).
|
|
291
|
+
*
|
|
292
|
+
* @param checkCompleted - Whether to also check completed:* labels for resume detection
|
|
293
|
+
*/
|
|
294
|
+
async pollRepo(owner, repo, checkCompleted) {
|
|
295
|
+
const client = this.createClient();
|
|
296
|
+
try {
|
|
297
|
+
// Check known process:* labels for issues (REST API, 2 calls per repo)
|
|
298
|
+
for (const processLabel of KNOWN_PROCESS_LABELS) {
|
|
299
|
+
const allIssues = await client.listIssuesWithLabel(owner, repo, processLabel);
|
|
300
|
+
const issues = filterByAssignee(allIssues, this.clusterGithubUsername, this.logger);
|
|
301
|
+
for (const issue of issues) {
|
|
302
|
+
const event = this.parseLabelEvent(processLabel, owner, repo, issue.number, issue.labels.map(l => l.name), 'poll');
|
|
303
|
+
if (event) {
|
|
304
|
+
await this.processLabelEvent(event);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// Check known completed:* labels for resume pairs (REST API, 13 calls per repo)
|
|
309
|
+
// Only checked periodically to conserve API rate limit
|
|
310
|
+
if (checkCompleted) {
|
|
311
|
+
for (const completedLabel of KNOWN_COMPLETED_LABELS) {
|
|
312
|
+
const allIssues = await client.listIssuesWithLabel(owner, repo, completedLabel);
|
|
313
|
+
const issues = filterByAssignee(allIssues, this.clusterGithubUsername, this.logger);
|
|
314
|
+
for (const issue of issues) {
|
|
315
|
+
const issueLabels = issue.labels.map(l => l.name);
|
|
316
|
+
const event = this.parseLabelEvent(completedLabel, owner, repo, issue.number, issueLabels, 'poll');
|
|
317
|
+
if (event) {
|
|
318
|
+
await this.processLabelEvent(event);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
this.logger.error({ err: error, owner, repo }, 'Error polling repository');
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
// ==========================================================================
|
|
329
|
+
// Adaptive Polling
|
|
330
|
+
// ==========================================================================
|
|
331
|
+
/**
|
|
332
|
+
* Record that a webhook event was received, updating health tracking.
|
|
333
|
+
*/
|
|
334
|
+
recordWebhookEvent() {
|
|
335
|
+
this.state.lastWebhookEvent = Date.now();
|
|
336
|
+
const wasUnhealthy = !this.state.webhookHealthy;
|
|
337
|
+
this.state.webhookHealthy = true;
|
|
338
|
+
if (wasUnhealthy) {
|
|
339
|
+
this.state.currentPollIntervalMs = this.state.basePollIntervalMs;
|
|
340
|
+
this.logger.info({ intervalMs: this.state.currentPollIntervalMs }, 'Webhook reconnected, restoring normal poll interval');
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Update adaptive polling interval based on webhook health.
|
|
345
|
+
*/
|
|
346
|
+
updateAdaptivePolling() {
|
|
347
|
+
if (this.state.lastWebhookEvent === null) {
|
|
348
|
+
// No webhook events yet — treat as healthy (no data, not unhealthy)
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const timeSinceLastWebhook = Date.now() - this.state.lastWebhookEvent;
|
|
352
|
+
const unhealthyThreshold = this.state.basePollIntervalMs * 2;
|
|
353
|
+
if (timeSinceLastWebhook > unhealthyThreshold && this.state.webhookHealthy) {
|
|
354
|
+
// Webhooks went unhealthy — increase poll frequency
|
|
355
|
+
this.state.webhookHealthy = false;
|
|
356
|
+
this.state.currentPollIntervalMs = Math.max(MIN_POLL_INTERVAL_MS, Math.floor(this.state.basePollIntervalMs / ADAPTIVE_DIVISOR));
|
|
357
|
+
this.logger.info({ intervalMs: this.state.currentPollIntervalMs, timeSinceLastWebhook }, 'Webhooks appear unhealthy, increasing poll frequency');
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
// ==========================================================================
|
|
361
|
+
// State Access
|
|
362
|
+
// ==========================================================================
|
|
363
|
+
getState() {
|
|
364
|
+
return { ...this.state };
|
|
365
|
+
}
|
|
366
|
+
// ==========================================================================
|
|
367
|
+
// Utilities
|
|
368
|
+
// ==========================================================================
|
|
369
|
+
sleep(ms, signal) {
|
|
370
|
+
return new Promise((resolve) => {
|
|
371
|
+
if (signal.aborted) {
|
|
372
|
+
resolve();
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const timer = setTimeout(resolve, ms);
|
|
376
|
+
// Clean up timer if abort is signaled
|
|
377
|
+
const onAbort = () => {
|
|
378
|
+
clearTimeout(timer);
|
|
379
|
+
resolve();
|
|
380
|
+
};
|
|
381
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Simple semaphore for concurrency limiting.
|
|
387
|
+
*/
|
|
388
|
+
class Semaphore {
|
|
389
|
+
count;
|
|
390
|
+
waiting = [];
|
|
391
|
+
constructor(max) {
|
|
392
|
+
this.count = max;
|
|
393
|
+
}
|
|
394
|
+
async acquire() {
|
|
395
|
+
if (this.count > 0) {
|
|
396
|
+
this.count--;
|
|
397
|
+
return () => this.release();
|
|
398
|
+
}
|
|
399
|
+
return new Promise((resolve) => {
|
|
400
|
+
this.waiting.push(() => {
|
|
401
|
+
this.count--;
|
|
402
|
+
resolve(() => this.release());
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
release() {
|
|
407
|
+
this.count++;
|
|
408
|
+
const next = this.waiting.shift();
|
|
409
|
+
if (next) {
|
|
410
|
+
next();
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
//# sourceMappingURL=label-monitor-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"label-monitor-service.js","sourceRoot":"","sources":["../../src/services/label-monitor-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAA4B,MAAM,8BAA8B,CAAC;AASzF,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD;;;GAGG;AACH,MAAM,oBAAoB,GAAG,eAAe;KACzC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;KAC1C,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAEpB,MAAM,sBAAsB,GAAG,eAAe;KAC3C,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;KAC5C,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAkBpB,MAAM,oBAAoB,GAAG,UAAU,CAAC;AACxC,MAAM,sBAAsB,GAAG,YAAY,CAAC;AAC5C,MAAM,wBAAwB,GAAG,cAAc,CAAC;AAChD,MAAM,uBAAuB,GAAG,mBAAmB,CAAC;AACpD,MAAM,oBAAoB,GAAG,KAAK,CAAC;AACnC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAE3B;;;GAGG;AACH,MAAM,OAAO,mBAAmB;IACb,MAAM,CAAS;IACf,YAAY,CAAsB;IAClC,YAAY,CAAe;IAC3B,YAAY,CAAe;IAC3B,OAAO,CAAsB;IAC7B,qBAAqB,CAAqB;IACnD,eAAe,GAA2B,IAAI,CAAC;IAC/C,cAAc,GAAG,CAAC,CAAC;IAE3B;;;;OAIG;IACK,MAAM,CAAU,wBAAwB,GAAG,CAAC,CAAC;IAE7C,KAAK,CAAe;IAE5B,YACE,MAAc,EACd,YAAiC,EACjC,YAA0B,EAC1B,YAA0B,EAC1B,MAAqB,EACrB,YAAgC,EAChC,qBAA8B;QAE9B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,qBAAqB,GAAG,qBAAqB,CAAC;QACnD,IAAI,CAAC,OAAO,GAAG;YACb,YAAY;YACZ,cAAc,EAAE,MAAM,CAAC,cAAc;YACrC,eAAe,EAAE,MAAM,CAAC,eAAe;YACvC,kBAAkB,EAAE,MAAM,CAAC,kBAAkB;SAC9C,CAAC;QAEF,IAAI,CAAC,KAAK,GAAG;YACX,SAAS,EAAE,KAAK;YAChB,cAAc,EAAE,IAAI;YACpB,gBAAgB,EAAE,IAAI;YACtB,qBAAqB,EAAE,MAAM,CAAC,cAAc;YAC5C,kBAAkB,EAAE,MAAM,CAAC,cAAc;SAC1C,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,gBAAgB;IAChB,6EAA6E;IAE7E;;;OAGG;IACH,eAAe,CACb,SAAiB,EACjB,KAAa,EACb,IAAY,EACZ,WAAmB,EACnB,WAAqB,EACrB,MAA0B;QAE1B,qCAAqC;QACrC,IAAI,SAAS,CAAC,UAAU,CAAC,oBAAoB,CAAC,EAAE,CAAC;YAC/C,MAAM,YAAY,GAAG,SAAS,CAAC,KAAK,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC;YAClE,IAAI,CAAC,YAAY;gBAAE,OAAO,IAAI,CAAC;YAE/B,OAAO;gBACL,IAAI,EAAE,SAAS;gBACf,KAAK;gBACL,IAAI;gBACJ,WAAW;gBACX,SAAS;gBACT,UAAU,EAAE,YAAY;gBACxB,MAAM;gBACN,WAAW;aACZ,CAAC;QACJ,CAAC;QAED,kDAAkD;QAClD,IAAI,SAAS,CAAC,UAAU,CAAC,sBAAsB,CAAC,EAAE,CAAC;YACjD,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC;YACjE,IAAI,CAAC,SAAS;gBAAE,OAAO,IAAI,CAAC;YAE5B,yCAAyC;YACzC,MAAM,YAAY,GAAG,GAAG,wBAAwB,GAAG,SAAS,EAAE,CAAC;YAC/D,IAAI,WAAW,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;gBACvC,OAAO;oBACL,IAAI,EAAE,QAAQ;oBACd,KAAK;oBACL,IAAI;oBACJ,WAAW;oBACX,SAAS;oBACT,UAAU,EAAE,SAAS;oBACrB,MAAM;oBACN,WAAW;iBACZ,CAAC;YACJ,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,EAAE,EACnF,6DAA6D,CAC9D,CAAC;QACJ,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED,6EAA6E;IAC7E,uBAAuB;IACvB,6EAA6E;IAE7E;;;;OAIG;IACH,KAAK,CAAC,8BAA8B,CAClC,KAAa,EACb,IAAY,EACZ,WAAmB,EACnB,SAAiB;QAEjB,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,EACvC,uDAAuD,CACxD,CAAC;QAEF,IAAI,WAAqB,CAAC;QAC1B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;YACnC,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;YAC9D,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC1E,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,EAAE,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,EAC3D,8DAA8D,CAC/D,CAAC;YACF,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,CAChC,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,SAAS,CAC5D,CAAC;QAEF,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,EACvC,8DAA8D,CAC/D,CAAC;YACF,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,EACzD,sEAAsE,CACvE,CAAC;QAEF,OAAO,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACvC,CAAC;IAED,6EAA6E;IAC7E,sBAAsB;IACtB,6EAA6E;IAE7E;;;;OAIG;IACK,yBAAyB,CAAC,WAAqB;QACrD,MAAM,qBAAqB,GAAG,WAAW,CAAC;QAC1C,MAAM,aAAa,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,qBAAqB,CAAC,CAAC,CAAC;QACjF,IAAI,aAAa,EAAE,CAAC;YAClB,OAAO,aAAa,CAAC,KAAK,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC;QAC3D,CAAC;QACD,OAAO,iBAAiB,CAAC;IAC3B,CAAC;IAED,6EAA6E;IAC7E,mBAAmB;IACnB,6EAA6E;IAE7E;;;OAGG;IACH,KAAK,CAAC,iBAAiB,CAAC,KAAiB;QACvC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;QAErE,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,EACtD,cAAc,IAAI,cAAc,CACjC,CAAC;QAEF,oEAAoE;QACpE,wEAAwE;QACxE,0EAA0E;QAC1E,iEAAiE;QACjE,MAAM,UAAU,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,UAAU,EAAE,CAAC;QAE5E,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,MAAM,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;QACtE,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;QAC9F,IAAI,WAAW,EAAE,CAAC;YAChB,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,EAAE,EAC/C,0BAA0B,CAC3B,CAAC;YACF,OAAO,KAAK,CAAC;QACf,CAAC;QAED,sEAAsE;QACtE,+DAA+D;QAC/D,MAAM,YAAY,GAAG,IAAI,KAAK,QAAQ;YACpC,CAAC,CAAC,IAAI,CAAC,yBAAyB,CAAC,KAAK,CAAC,WAAW,CAAC;YACnD,CAAC,CAAC,UAAU,CAAC;QAEf,IAAI,IAAI,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC;YACjF,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,iBAAiB,EAAE,EAC5D,kEAAkE,CACnE,CAAC;QACJ,CAAC;QAED,mBAAmB;QACnB,MAAM,SAAS,GAAc;YAC3B,KAAK;YACL,IAAI;YACJ,WAAW;YACX,YAAY;YACZ,OAAO,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU;YACpD,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE;YACpB,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACrC,CAAC;QAEF,UAAU;QACV,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAC3C,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,YAAY,EAAE,EACtE,gBAAgB,CACjB,CAAC;QAEF,8BAA8B;QAC9B,MAAM,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;QAE5E,iCAAiC;QACjC,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QAEnC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,oFAAoF;YACpF,mFAAmF;YACnF,mDAAmD;YACnD,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;gBAC9D,MAAM,eAAe,GAAG,KAAK,CAAC,MAAM;qBACjC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;qBAC5C,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC,CAAC;gBAE3D,MAAM,cAAc,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,aAAa,EAAE,GAAG,eAAe,CAAC,CAAC;gBAC5E,MAAM,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,cAAc,CAAC,CAAC;gBACpE,MAAM,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE;oBAC/C,uBAAuB;oBACvB,YAAY,UAAU,EAAE;iBACzB,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,EACxC,+CAA+C,CAChD,CAAC;YACJ,CAAC;QACH,CAAC;QACD,wEAAwE;QACxE,sEAAsE;QACtE,wEAAwE;QAExE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,6EAA6E;IAC7E,UAAU;IACV,6EAA6E;IAE7E;;OAEG;IACH,KAAK,CAAC,YAAY;QAChB,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;YACzB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;YAC5C,OAAO;QACT,CAAC;QAED,MAAM,EAAE,GAAG,IAAI,eAAe,EAAE,CAAC;QACjC,IAAI,CAAC,eAAe,GAAG,EAAE,CAAC;QAC1B,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC;QAC5B,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,EAAE,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,qBAAqB,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,MAAM,EAAE,EACzF,gCAAgC,CACjC,CAAC;QAEF,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YAC1B,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;YACpB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,EAAE,GAAG,EAAE,KAAK,EAAE,EACd,yBAAyB,CAC1B,CAAC;YACJ,CAAC;YAED,0CAA0C;YAC1C,IAAI,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC;gBACjC,IAAI,CAAC,qBAAqB,EAAE,CAAC;YAC/B,CAAC;YAED,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,qBAAqB,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;QAChE,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC;QAC7B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IAC3C,CAAC;IAED;;OAEG;IACH,WAAW;QACT,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACzB,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;YAC7B,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;YAC5B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,IAAI;QACR,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC;QACxC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAE/B,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,MAAM,cAAc,GAAG,IAAI,CAAC,cAAc,GAAG,mBAAmB,CAAC,wBAAwB,KAAK,CAAC,CAAC;QAEhG,iDAAiD;QACjD,MAAM,SAAS,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;QAEjE,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,CAC9C,SAAS,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;YACzC,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;YACnD,CAAC;oBAAS,CAAC;gBACT,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC,CAAC,CACH,CAAC;QAEF,MAAM,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IACtC,CAAC;IAED;;;;;;OAMG;IACK,KAAK,CAAC,QAAQ,CAAC,KAAa,EAAE,IAAY,EAAE,cAAuB;QACzE,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QAEnC,IAAI,CAAC;YACH,uEAAuE;YACvE,KAAK,MAAM,YAAY,IAAI,oBAAoB,EAAE,CAAC;gBAChD,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,KAAK,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC;gBAC9E,MAAM,MAAM,GAAG,gBAAgB,CAAC,SAAS,EAAE,IAAI,CAAC,qBAAqB,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;gBACpF,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;oBAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,CAChC,YAAY,EACZ,KAAK,EACL,IAAI,EACJ,KAAK,CAAC,MAAM,EACZ,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAC7B,MAAM,CACP,CAAC;oBACF,IAAI,KAAK,EAAE,CAAC;wBACV,MAAM,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;oBACtC,CAAC;gBACH,CAAC;YACH,CAAC;YAED,gFAAgF;YAChF,uDAAuD;YACvD,IAAI,cAAc,EAAE,CAAC;gBACnB,KAAK,MAAM,cAAc,IAAI,sBAAsB,EAAE,CAAC;oBACpD,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,KAAK,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;oBAChF,MAAM,MAAM,GAAG,gBAAgB,CAAC,SAAS,EAAE,IAAI,CAAC,qBAAqB,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;oBACpF,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;wBAC3B,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;wBAClD,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,CAChC,cAAc,EACd,KAAK,EACL,IAAI,EACJ,KAAK,CAAC,MAAM,EACZ,WAAW,EACX,MAAM,CACP,CAAC;wBACF,IAAI,KAAK,EAAE,CAAC;4BACV,MAAM,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;wBACtC,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAC3B,0BAA0B,CAC3B,CAAC;QACJ,CAAC;IACH,CAAC;IAED,6EAA6E;IAC7E,mBAAmB;IACnB,6EAA6E;IAE7E;;OAEG;IACH,kBAAkB;QAChB,IAAI,CAAC,KAAK,CAAC,gBAAgB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzC,MAAM,YAAY,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC;QAChD,IAAI,CAAC,KAAK,CAAC,cAAc,GAAG,IAAI,CAAC;QAEjC,IAAI,YAAY,EAAE,CAAC;YACjB,IAAI,CAAC,KAAK,CAAC,qBAAqB,GAAG,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC;YACjE,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,EAAE,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,qBAAqB,EAAE,EAChD,qDAAqD,CACtD,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;OAEG;IACK,qBAAqB;QAC3B,IAAI,IAAI,CAAC,KAAK,CAAC,gBAAgB,KAAK,IAAI,EAAE,CAAC;YACzC,oEAAoE;YACpE,OAAO;QACT,CAAC;QAED,MAAM,oBAAoB,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC;QACtE,MAAM,kBAAkB,GAAG,IAAI,CAAC,KAAK,CAAC,kBAAkB,GAAG,CAAC,CAAC;QAE7D,IAAI,oBAAoB,GAAG,kBAAkB,IAAI,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;YAC3E,oDAAoD;YACpD,IAAI,CAAC,KAAK,CAAC,cAAc,GAAG,KAAK,CAAC;YAClC,IAAI,CAAC,KAAK,CAAC,qBAAqB,GAAG,IAAI,CAAC,GAAG,CACzC,oBAAoB,EACpB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,kBAAkB,GAAG,gBAAgB,CAAC,CAC7D,CAAC;YACF,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,EAAE,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,qBAAqB,EAAE,oBAAoB,EAAE,EACtE,sDAAsD,CACvD,CAAC;QACJ,CAAC;IACH,CAAC;IAED,6EAA6E;IAC7E,eAAe;IACf,6EAA6E;IAE7E,QAAQ;QACN,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;IAC3B,CAAC;IAED,6EAA6E;IAC7E,YAAY;IACZ,6EAA6E;IAErE,KAAK,CAAC,EAAU,EAAE,MAAmB;QAC3C,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YACnC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnB,OAAO,EAAE,CAAC;gBACV,OAAO;YACT,CAAC;YAED,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YAEtC,sCAAsC;YACtC,MAAM,OAAO,GAAG,GAAG,EAAE;gBACnB,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC;YAEF,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5D,CAAC,CAAC,CAAC;IACL,CAAC;;AAGH;;GAEG;AACH,MAAM,SAAS;IACL,KAAK,CAAS;IACd,OAAO,GAAsB,EAAE,CAAC;IAExC,YAAY,GAAW;QACrB,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC;IACnB,CAAC;IAED,KAAK,CAAC,OAAO;QACX,IAAI,IAAI,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;YACnB,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAC9B,CAAC;QAED,OAAO,IAAI,OAAO,CAAa,CAAC,OAAO,EAAE,EAAE;YACzC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE;gBACrB,IAAI,CAAC,KAAK,EAAE,CAAC;gBACb,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;YAChC,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,OAAO;QACb,IAAI,CAAC,KAAK,EAAE,CAAC;QACb,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QAClC,IAAI,IAAI,EAAE,CAAC;YACT,IAAI,EAAE,CAAC;QACT,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { GitHubClientFactory } from '@generacy-ai/workflow-engine';
|
|
2
|
+
/**
|
|
3
|
+
* Result of syncing a single label
|
|
4
|
+
*/
|
|
5
|
+
export interface LabelSyncResult {
|
|
6
|
+
name: string;
|
|
7
|
+
action: 'created' | 'updated' | 'unchanged';
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Result of syncing labels for a single repository
|
|
11
|
+
*/
|
|
12
|
+
export interface RepoSyncResult {
|
|
13
|
+
owner: string;
|
|
14
|
+
repo: string;
|
|
15
|
+
success: boolean;
|
|
16
|
+
created: number;
|
|
17
|
+
updated: number;
|
|
18
|
+
unchanged: number;
|
|
19
|
+
error?: string;
|
|
20
|
+
results: LabelSyncResult[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Result of syncing labels across all repositories
|
|
24
|
+
*/
|
|
25
|
+
export interface SyncAllResult {
|
|
26
|
+
totalRepos: number;
|
|
27
|
+
successfulRepos: number;
|
|
28
|
+
failedRepos: number;
|
|
29
|
+
results: RepoSyncResult[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Logger interface matching Pino/Fastify logger shape
|
|
33
|
+
*/
|
|
34
|
+
interface Logger {
|
|
35
|
+
info(msg: string): void;
|
|
36
|
+
warn(msg: string): void;
|
|
37
|
+
error(msg: string): void;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Service for syncing workflow labels across multiple repositories.
|
|
41
|
+
* Ensures all configured repos have the correct set of label protocol labels.
|
|
42
|
+
*/
|
|
43
|
+
export declare class LabelSyncService {
|
|
44
|
+
private readonly logger;
|
|
45
|
+
private readonly createClient;
|
|
46
|
+
private readonly syncedRepos;
|
|
47
|
+
constructor(logger: Logger, createClient: GitHubClientFactory);
|
|
48
|
+
/**
|
|
49
|
+
* Sync labels for a single repository.
|
|
50
|
+
* Lists existing labels, diffs against WORKFLOW_LABELS, creates/updates as needed.
|
|
51
|
+
*/
|
|
52
|
+
syncRepo(owner: string, repo: string): Promise<RepoSyncResult>;
|
|
53
|
+
/**
|
|
54
|
+
* Sync labels for all provided repositories sequentially.
|
|
55
|
+
* Per-repo errors are captured without failing the batch.
|
|
56
|
+
*/
|
|
57
|
+
syncAll(repos: Array<{
|
|
58
|
+
owner: string;
|
|
59
|
+
repo: string;
|
|
60
|
+
}>): Promise<SyncAllResult>;
|
|
61
|
+
/**
|
|
62
|
+
* Sync a new repo if not already tracked in this session.
|
|
63
|
+
* Convenience method for the "new repo added" use case.
|
|
64
|
+
*/
|
|
65
|
+
syncNewRepo(owner: string, repo: string): Promise<RepoSyncResult | null>;
|
|
66
|
+
/**
|
|
67
|
+
* Force sync a repo, bypassing the tracking check.
|
|
68
|
+
*/
|
|
69
|
+
forceSync(owner: string, repo: string): Promise<RepoSyncResult>;
|
|
70
|
+
/**
|
|
71
|
+
* Reset the synced repos tracking set.
|
|
72
|
+
*/
|
|
73
|
+
resetTracking(): void;
|
|
74
|
+
}
|
|
75
|
+
export {};
|
|
76
|
+
//# sourceMappingURL=label-sync-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"label-sync-service.d.ts","sourceRoot":"","sources":["../../src/services/label-sync-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAGxE;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,CAAC;CAC7C;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,eAAe,EAAE,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,cAAc,EAAE,CAAC;CAC3B;AAED;;GAEG;AACH,UAAU,MAAM;IACd,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;GAGG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsB;IACnD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA0B;gBAE1C,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,mBAAmB;IAK7D;;;OAGG;IACG,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAwCpE;;;OAGG;IACG,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC;IA8BpF;;;OAGG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAU9E;;OAEG;IACG,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAIrE;;OAEG;IACH,aAAa,IAAI,IAAI;CAGtB"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { WORKFLOW_LABELS } from '@generacy-ai/workflow-engine';
|
|
2
|
+
/**
|
|
3
|
+
* Service for syncing workflow labels across multiple repositories.
|
|
4
|
+
* Ensures all configured repos have the correct set of label protocol labels.
|
|
5
|
+
*/
|
|
6
|
+
export class LabelSyncService {
|
|
7
|
+
logger;
|
|
8
|
+
createClient;
|
|
9
|
+
syncedRepos = new Set();
|
|
10
|
+
constructor(logger, createClient) {
|
|
11
|
+
this.logger = logger;
|
|
12
|
+
this.createClient = createClient;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Sync labels for a single repository.
|
|
16
|
+
* Lists existing labels, diffs against WORKFLOW_LABELS, creates/updates as needed.
|
|
17
|
+
*/
|
|
18
|
+
async syncRepo(owner, repo) {
|
|
19
|
+
const client = this.createClient();
|
|
20
|
+
const results = [];
|
|
21
|
+
let created = 0;
|
|
22
|
+
let updated = 0;
|
|
23
|
+
let unchanged = 0;
|
|
24
|
+
try {
|
|
25
|
+
const existingLabels = await client.listLabels(owner, repo);
|
|
26
|
+
const existingMap = new Map(existingLabels.map(l => [l.name, l]));
|
|
27
|
+
for (const label of WORKFLOW_LABELS) {
|
|
28
|
+
const existing = existingMap.get(label.name);
|
|
29
|
+
if (!existing) {
|
|
30
|
+
await client.createLabel(owner, repo, label.name, label.color, label.description);
|
|
31
|
+
results.push({ name: label.name, action: 'created' });
|
|
32
|
+
created++;
|
|
33
|
+
}
|
|
34
|
+
else if (existing.color !== label.color || existing.description !== label.description) {
|
|
35
|
+
await client.updateLabel(owner, repo, label.name, {
|
|
36
|
+
color: label.color,
|
|
37
|
+
description: label.description,
|
|
38
|
+
});
|
|
39
|
+
results.push({ name: label.name, action: 'updated' });
|
|
40
|
+
updated++;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
results.push({ name: label.name, action: 'unchanged' });
|
|
44
|
+
unchanged++;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
this.syncedRepos.add(`${owner}/${repo}`);
|
|
48
|
+
return { owner, repo, success: true, created, updated, unchanged, results };
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
52
|
+
return { owner, repo, success: false, created, updated, unchanged, error: errorMsg, results };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Sync labels for all provided repositories sequentially.
|
|
57
|
+
* Per-repo errors are captured without failing the batch.
|
|
58
|
+
*/
|
|
59
|
+
async syncAll(repos) {
|
|
60
|
+
const results = [];
|
|
61
|
+
let successfulRepos = 0;
|
|
62
|
+
let failedRepos = 0;
|
|
63
|
+
for (const { owner, repo } of repos) {
|
|
64
|
+
const key = `${owner}/${repo}`;
|
|
65
|
+
if (this.syncedRepos.has(key)) {
|
|
66
|
+
this.logger.info(`Skipping already-synced repo: ${key}`);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
this.logger.info(`Syncing labels for ${key}...`);
|
|
70
|
+
const result = await this.syncRepo(owner, repo);
|
|
71
|
+
results.push(result);
|
|
72
|
+
if (result.success) {
|
|
73
|
+
successfulRepos++;
|
|
74
|
+
this.logger.info(`${key}: ${result.created} created, ${result.updated} updated, ${result.unchanged} unchanged`);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
failedRepos++;
|
|
78
|
+
this.logger.warn(`${key}: sync failed — ${result.error}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { totalRepos: repos.length, successfulRepos, failedRepos, results };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Sync a new repo if not already tracked in this session.
|
|
85
|
+
* Convenience method for the "new repo added" use case.
|
|
86
|
+
*/
|
|
87
|
+
async syncNewRepo(owner, repo) {
|
|
88
|
+
const key = `${owner}/${repo}`;
|
|
89
|
+
if (this.syncedRepos.has(key)) {
|
|
90
|
+
this.logger.info(`Repo ${key} already synced in this session, skipping.`);
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return this.syncRepo(owner, repo);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Force sync a repo, bypassing the tracking check.
|
|
97
|
+
*/
|
|
98
|
+
async forceSync(owner, repo) {
|
|
99
|
+
return this.syncRepo(owner, repo);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Reset the synced repos tracking set.
|
|
103
|
+
*/
|
|
104
|
+
resetTracking() {
|
|
105
|
+
this.syncedRepos.clear();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=label-sync-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"label-sync-service.js","sourceRoot":"","sources":["../../src/services/label-sync-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Redis } from 'ioredis';
|
|
2
|
+
import type { PhaseTracker } from '../types/index.js';
|
|
3
|
+
interface Logger {
|
|
4
|
+
info(msg: string): void;
|
|
5
|
+
info(obj: Record<string, unknown>, msg: string): void;
|
|
6
|
+
warn(msg: string): void;
|
|
7
|
+
warn(obj: Record<string, unknown>, msg: string): void;
|
|
8
|
+
error(msg: string): void;
|
|
9
|
+
error(obj: Record<string, unknown>, msg: string): void;
|
|
10
|
+
}
|
|
11
|
+
export interface PhaseTrackerOptions {
|
|
12
|
+
/** TTL for dedup keys in seconds (default: 86400 = 24h) */
|
|
13
|
+
ttlSeconds?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Redis-backed phase tracker for deduplication.
|
|
17
|
+
* Uses SET with TTL to prevent duplicate processing of the same issue+phase.
|
|
18
|
+
* Gracefully degrades when Redis is unavailable (treats as "not duplicate").
|
|
19
|
+
*/
|
|
20
|
+
export declare class PhaseTrackerService implements PhaseTracker {
|
|
21
|
+
private readonly redis;
|
|
22
|
+
private readonly logger;
|
|
23
|
+
private readonly ttlSeconds;
|
|
24
|
+
constructor(logger: Logger, redis: Redis | null, options?: PhaseTrackerOptions);
|
|
25
|
+
private buildKey;
|
|
26
|
+
isDuplicate(owner: string, repo: string, issue: number, phase: string): Promise<boolean>;
|
|
27
|
+
clear(owner: string, repo: string, issue: number, phase: string): Promise<void>;
|
|
28
|
+
tryMarkProcessed(owner: string, repo: string, issue: number, phase: string): Promise<boolean>;
|
|
29
|
+
markProcessed(owner: string, repo: string, issue: number, phase: string): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=phase-tracker-service.d.ts.map
|