@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,391 @@
|
|
|
1
|
+
import { createGitHubClient } from '@generacy-ai/workflow-engine';
|
|
2
|
+
import { OutputCapture } from './output-capture.js';
|
|
3
|
+
import { RepoCheckout } from './repo-checkout.js';
|
|
4
|
+
/**
|
|
5
|
+
* Handles the `address-pr-feedback` command.
|
|
6
|
+
*
|
|
7
|
+
* Processing flow:
|
|
8
|
+
* 1. Extract PR number from queue item metadata
|
|
9
|
+
* 2. Fetch the PR to get the branch name
|
|
10
|
+
* 3. Switch to the PR branch (not default branch)
|
|
11
|
+
* 4. Fetch fresh unresolved review threads
|
|
12
|
+
* 5. Build a structured prompt with all unresolved comments
|
|
13
|
+
* 6. Spawn Claude CLI to address the feedback
|
|
14
|
+
* 7. Stage, commit, and push changes to the PR branch
|
|
15
|
+
* 8. Reply to each review thread (never resolve)
|
|
16
|
+
* 9. Remove the `waiting-for:address-pr-feedback` label
|
|
17
|
+
*
|
|
18
|
+
* Error handling strategy (T019):
|
|
19
|
+
*
|
|
20
|
+
* **FR-013: Timeout handling**
|
|
21
|
+
* - If CLI times out: push any partial changes that were made
|
|
22
|
+
* - Keep `waiting-for:address-pr-feedback` label (don't remove)
|
|
23
|
+
* - Label retention ensures the task will be retried on next detection cycle
|
|
24
|
+
* - Log timeout warning with structured context
|
|
25
|
+
*
|
|
26
|
+
* **FR-007: Reply failure handling**
|
|
27
|
+
* - If posting replies fails for some threads: still remove label
|
|
28
|
+
* - Log warnings for each failed reply
|
|
29
|
+
* - Partial reply success is acceptable (code changes were pushed successfully)
|
|
30
|
+
* - Handler does not throw on reply failures
|
|
31
|
+
*
|
|
32
|
+
* **SC-006: Thread resolution prevention**
|
|
33
|
+
* - Only use `replyToPRComment()` API — never call any thread-resolve API
|
|
34
|
+
* - Human reviewer will resolve threads after verifying changes
|
|
35
|
+
* - Comments in code explicitly reference SC-006 for clarity
|
|
36
|
+
*
|
|
37
|
+
* **Structured logging**
|
|
38
|
+
* - All operations logged with structured context (prNumber, issueNumber, owner, repo)
|
|
39
|
+
* - Error logs include full error messages and relevant context
|
|
40
|
+
* - Success/failure states clearly logged for observability
|
|
41
|
+
* - All FR/SC references included in log messages for traceability
|
|
42
|
+
*
|
|
43
|
+
* **Critical operation failures**
|
|
44
|
+
* - PR fetch failure: throw (cannot proceed without PR details)
|
|
45
|
+
* - Branch switch failure: throw (wrong branch would cause issues)
|
|
46
|
+
* - Comment fetch failure: throw (cannot address unknown feedback)
|
|
47
|
+
* - Push failure: log error but don't throw (allows retry via label)
|
|
48
|
+
*
|
|
49
|
+
* **Non-critical operation failures**
|
|
50
|
+
* - No unresolved threads: remove label, early return (success case)
|
|
51
|
+
* - Reply posting: log warning, continue (FR-007)
|
|
52
|
+
* - Label removal: log warning, continue (non-fatal)
|
|
53
|
+
*/
|
|
54
|
+
export class PrFeedbackHandler {
|
|
55
|
+
config;
|
|
56
|
+
logger;
|
|
57
|
+
processFactory;
|
|
58
|
+
sseEmitter;
|
|
59
|
+
repoCheckout;
|
|
60
|
+
constructor(config, logger, processFactory, sseEmitter) {
|
|
61
|
+
this.config = config;
|
|
62
|
+
this.logger = logger;
|
|
63
|
+
this.processFactory = processFactory;
|
|
64
|
+
this.sseEmitter = sseEmitter;
|
|
65
|
+
this.repoCheckout = new RepoCheckout(config.workspaceDir, logger);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Process a PR feedback addressing task.
|
|
69
|
+
*
|
|
70
|
+
* @param item - Queue item with `command: 'address-pr-feedback'`
|
|
71
|
+
* @param checkoutPath - Path to the already-checked-out repository
|
|
72
|
+
*/
|
|
73
|
+
async handle(item, checkoutPath) {
|
|
74
|
+
const { owner, repo, issueNumber } = item;
|
|
75
|
+
const metadata = item.metadata;
|
|
76
|
+
if (!metadata?.prNumber) {
|
|
77
|
+
throw new Error('Missing prNumber in metadata for address-pr-feedback command');
|
|
78
|
+
}
|
|
79
|
+
const { prNumber } = metadata;
|
|
80
|
+
const workflowId = `${owner}/${repo}#${issueNumber}`;
|
|
81
|
+
this.logger.info({ prNumber, issueNumber, owner, repo }, 'Starting PR feedback addressing');
|
|
82
|
+
// Create GitHub client scoped to checkout path
|
|
83
|
+
const github = createGitHubClient(checkoutPath);
|
|
84
|
+
try {
|
|
85
|
+
// 1. Fetch the PR to get branch name
|
|
86
|
+
let pr;
|
|
87
|
+
try {
|
|
88
|
+
pr = await github.getPullRequest(owner, repo, prNumber);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
this.logger.error({ error: String(error), prNumber, owner, repo }, 'Failed to fetch PR details');
|
|
92
|
+
throw new Error(`Failed to fetch PR #${prNumber}: ${String(error)}`);
|
|
93
|
+
}
|
|
94
|
+
const branchName = pr.head.ref;
|
|
95
|
+
this.logger.info({ prNumber, branchName }, 'PR branch identified');
|
|
96
|
+
// 2. Switch to the PR branch
|
|
97
|
+
try {
|
|
98
|
+
await this.repoCheckout.switchBranch(checkoutPath, branchName);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
this.logger.error({ error: String(error), prNumber, branchName, checkoutPath }, 'Failed to switch to PR branch');
|
|
102
|
+
throw new Error(`Failed to switch to branch ${branchName}: ${String(error)}`);
|
|
103
|
+
}
|
|
104
|
+
// 3. Fetch fresh unresolved review threads
|
|
105
|
+
let allComments;
|
|
106
|
+
let unresolvedComments;
|
|
107
|
+
try {
|
|
108
|
+
allComments = await github.getPRComments(owner, repo, prNumber);
|
|
109
|
+
unresolvedComments = allComments.filter((c) => c.resolved === false);
|
|
110
|
+
this.logger.info({ prNumber, totalComments: allComments.length, unresolved: unresolvedComments.length }, 'Fetched PR review comments');
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
this.logger.error({ error: String(error), prNumber, owner, repo }, 'Failed to fetch PR review comments');
|
|
114
|
+
throw new Error(`Failed to fetch review comments for PR #${prNumber}: ${String(error)}`);
|
|
115
|
+
}
|
|
116
|
+
// 4. If no unresolved threads, remove label and return early
|
|
117
|
+
if (unresolvedComments.length === 0) {
|
|
118
|
+
this.logger.info({ prNumber, issueNumber }, 'No unresolved threads found — removing label and exiting');
|
|
119
|
+
await this.removeFeedbackLabel(github, owner, repo, issueNumber);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// 5. Build structured prompt
|
|
123
|
+
const prompt = this.buildFeedbackPrompt(unresolvedComments, prNumber, issueNumber);
|
|
124
|
+
// 6. Spawn Claude CLI to address feedback
|
|
125
|
+
const success = await this.spawnClaudeForFeedback(checkoutPath, prompt, workflowId);
|
|
126
|
+
// 7. Commit and push changes (even on timeout — partial completion strategy)
|
|
127
|
+
// FR-013: Push partial changes on timeout to preserve work and enable retry
|
|
128
|
+
let hasChanges = false;
|
|
129
|
+
try {
|
|
130
|
+
hasChanges = await this.commitAndPushChanges(github, checkoutPath, branchName, prNumber, issueNumber);
|
|
131
|
+
if (hasChanges) {
|
|
132
|
+
this.logger.info({ prNumber, issueNumber, success }, 'Successfully pushed changes to PR branch');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
this.logger.error({ error: String(error), prNumber, issueNumber, branchName }, 'Failed to commit and push changes — partial work may be lost');
|
|
137
|
+
// Don't throw here — we want to continue with reply attempt and label management
|
|
138
|
+
// The label will be kept for retry if success=false
|
|
139
|
+
}
|
|
140
|
+
if (!hasChanges && !success) {
|
|
141
|
+
this.logger.warn({ prNumber, issueNumber }, 'No changes made and CLI did not complete successfully — will retry');
|
|
142
|
+
}
|
|
143
|
+
// 8. Reply to review threads (only if CLI completed successfully)
|
|
144
|
+
// FR-007: Partial reply failure is acceptable — we still remove the label
|
|
145
|
+
if (success) {
|
|
146
|
+
try {
|
|
147
|
+
await this.replyToThreads(github, owner, repo, prNumber, unresolvedComments);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
// FR-007: Even if reply posting fails, we consider the task complete
|
|
151
|
+
// and remove the label. The code changes were pushed successfully.
|
|
152
|
+
this.logger.warn({ error: String(error), prNumber, issueNumber }, 'Failed to post some or all thread replies — partial success acceptable');
|
|
153
|
+
// Continue to label removal
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
this.logger.warn({ prNumber, issueNumber }, 'Skipping thread replies due to CLI timeout or failure — will retry on next cycle');
|
|
158
|
+
}
|
|
159
|
+
// 9. Remove label (only if successful, otherwise keep for retry)
|
|
160
|
+
// FR-013: Keep label on timeout/failure to enable retry
|
|
161
|
+
if (success) {
|
|
162
|
+
await this.removeFeedbackLabel(github, owner, repo, issueNumber);
|
|
163
|
+
this.logger.info({ prNumber, issueNumber }, 'PR feedback addressing completed successfully');
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
this.logger.info({ prNumber, issueNumber, hasChanges }, 'Keeping waiting-for:address-pr-feedback label for retry (CLI did not complete successfully)');
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
this.logger.error({ error: String(error), prNumber, issueNumber, owner, repo }, 'Error processing PR feedback — task failed');
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Build a structured prompt containing all unresolved review comments.
|
|
176
|
+
*
|
|
177
|
+
* The prompt instructs Claude to:
|
|
178
|
+
* - Read each review comment
|
|
179
|
+
* - Make the necessary code changes
|
|
180
|
+
* - Commit the changes
|
|
181
|
+
* - NOT resolve any threads (human reviewer will resolve)
|
|
182
|
+
*/
|
|
183
|
+
buildFeedbackPrompt(comments, prNumber, issueNumber) {
|
|
184
|
+
const commentList = comments
|
|
185
|
+
.map((c, idx) => {
|
|
186
|
+
const location = c.path && c.line
|
|
187
|
+
? `${c.path}:${c.line}`
|
|
188
|
+
: c.path || 'general comment';
|
|
189
|
+
return `${idx + 1}. **${c.author}** (${location}):\n ${c.body}`;
|
|
190
|
+
})
|
|
191
|
+
.join('\n\n');
|
|
192
|
+
return `You are addressing PR review feedback for PR #${prNumber} (linked to issue #${issueNumber}).
|
|
193
|
+
|
|
194
|
+
The following unresolved review comments need to be addressed:
|
|
195
|
+
|
|
196
|
+
${commentList}
|
|
197
|
+
|
|
198
|
+
**Instructions:**
|
|
199
|
+
1. Read and understand each review comment above
|
|
200
|
+
2. Make the necessary code changes to address each comment
|
|
201
|
+
3. The changes will be automatically committed and pushed to the PR branch
|
|
202
|
+
4. Do NOT resolve any review threads — the human reviewer will resolve them after verifying your changes
|
|
203
|
+
|
|
204
|
+
Please proceed with addressing the feedback.`;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Spawn Claude CLI to address PR feedback.
|
|
208
|
+
*
|
|
209
|
+
* Returns true if the CLI completed successfully, false on timeout or failure.
|
|
210
|
+
* FR-013: On timeout, partial changes are pushed and label is kept for retry.
|
|
211
|
+
*/
|
|
212
|
+
async spawnClaudeForFeedback(checkoutPath, prompt, workflowId) {
|
|
213
|
+
const args = [
|
|
214
|
+
'--headless',
|
|
215
|
+
'--output', 'json',
|
|
216
|
+
'--print', 'all',
|
|
217
|
+
'--max-turns', String(this.config.maxTurns),
|
|
218
|
+
'--prompt', prompt,
|
|
219
|
+
];
|
|
220
|
+
this.logger.info({ cwd: checkoutPath, maxTurns: this.config.maxTurns, timeoutMs: this.config.phaseTimeoutMs }, 'Spawning Claude CLI for PR feedback');
|
|
221
|
+
let child;
|
|
222
|
+
try {
|
|
223
|
+
child = this.processFactory.spawn('claude', args, {
|
|
224
|
+
cwd: checkoutPath,
|
|
225
|
+
env: {},
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
this.logger.error({ error: String(error), cwd: checkoutPath }, 'Failed to spawn Claude CLI process');
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
// Set up output capture for SSE events
|
|
233
|
+
const outputCapture = new OutputCapture(workflowId, this.logger, this.sseEmitter);
|
|
234
|
+
// Capture stdout
|
|
235
|
+
if (child.stdout) {
|
|
236
|
+
child.stdout.on('data', (data) => {
|
|
237
|
+
outputCapture.processChunk(typeof data === 'string' ? data : data.toString('utf-8'));
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
// Capture stderr for error diagnostics
|
|
241
|
+
if (child.stderr) {
|
|
242
|
+
let stderrBuffer = '';
|
|
243
|
+
child.stderr.on('data', (data) => {
|
|
244
|
+
stderrBuffer += typeof data === 'string' ? data : data.toString('utf-8');
|
|
245
|
+
});
|
|
246
|
+
// Log stderr on process exit if non-empty
|
|
247
|
+
child.exitPromise.finally(() => {
|
|
248
|
+
if (stderrBuffer.trim()) {
|
|
249
|
+
this.logger.debug({ stderr: stderrBuffer.trim() }, 'Claude CLI stderr output');
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
// Set up timeout
|
|
254
|
+
// FR-013: On timeout, we return false and the caller will push partial changes
|
|
255
|
+
let timedOut = false;
|
|
256
|
+
const timeoutTimer = setTimeout(() => {
|
|
257
|
+
timedOut = true;
|
|
258
|
+
this.logger.warn({ pid: child.pid, timeoutMs: this.config.phaseTimeoutMs }, 'PR feedback CLI timed out (FR-013) — sending SIGTERM, partial changes will be pushed');
|
|
259
|
+
child.kill('SIGTERM');
|
|
260
|
+
// Force kill after grace period
|
|
261
|
+
setTimeout(() => {
|
|
262
|
+
if (child.pid) {
|
|
263
|
+
this.logger.warn({ pid: child.pid, gracePeriodMs: this.config.shutdownGracePeriodMs }, 'Grace period expired, sending SIGKILL');
|
|
264
|
+
child.kill('SIGKILL');
|
|
265
|
+
}
|
|
266
|
+
}, this.config.shutdownGracePeriodMs);
|
|
267
|
+
}, this.config.phaseTimeoutMs);
|
|
268
|
+
try {
|
|
269
|
+
const exitCode = await child.exitPromise;
|
|
270
|
+
clearTimeout(timeoutTimer);
|
|
271
|
+
outputCapture.flush();
|
|
272
|
+
const success = exitCode === 0;
|
|
273
|
+
if (timedOut) {
|
|
274
|
+
// FR-013: Timeout scenario — partial completion strategy
|
|
275
|
+
this.logger.warn({ exitCode, timeoutMs: this.config.phaseTimeoutMs }, 'CLI timed out — returning false to trigger partial completion strategy (push changes, keep label)');
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
if (!success) {
|
|
279
|
+
this.logger.warn({ exitCode }, 'CLI exited with non-zero code — returning false to keep label for retry');
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
this.logger.info({ exitCode }, 'CLI completed successfully');
|
|
283
|
+
}
|
|
284
|
+
return success;
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
clearTimeout(timeoutTimer);
|
|
288
|
+
this.logger.error({ error: String(error), timedOut }, 'Error waiting for CLI process — returning false');
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Stage all changes, commit, and push to the PR branch.
|
|
294
|
+
*
|
|
295
|
+
* Returns true if there were changes to commit, false otherwise.
|
|
296
|
+
*/
|
|
297
|
+
async commitAndPushChanges(github, checkoutPath, branchName, prNumber, issueNumber) {
|
|
298
|
+
let status;
|
|
299
|
+
try {
|
|
300
|
+
status = await github.getStatus();
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
this.logger.error({ error: String(error), checkoutPath }, 'Failed to get git status');
|
|
304
|
+
throw new Error(`Failed to get git status: ${String(error)}`);
|
|
305
|
+
}
|
|
306
|
+
if (!status.has_changes) {
|
|
307
|
+
this.logger.info({ prNumber, issueNumber }, 'No changes to commit — skipping commit/push');
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
this.logger.info({ prNumber, staged: status.staged.length, unstaged: status.unstaged.length, untracked: status.untracked.length }, 'Staging and committing changes');
|
|
311
|
+
// Stage all changes
|
|
312
|
+
try {
|
|
313
|
+
await github.stageAll();
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
this.logger.error({ error: String(error), prNumber }, 'Failed to stage changes');
|
|
317
|
+
throw new Error(`Failed to stage changes: ${String(error)}`);
|
|
318
|
+
}
|
|
319
|
+
// Commit with clear message
|
|
320
|
+
const commitMessage = `Address PR #${prNumber} review feedback
|
|
321
|
+
|
|
322
|
+
Automated feedback addressing for issue #${issueNumber}.
|
|
323
|
+
|
|
324
|
+
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>`;
|
|
325
|
+
try {
|
|
326
|
+
await github.commit(commitMessage);
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
this.logger.error({ error: String(error), prNumber }, 'Failed to commit changes');
|
|
330
|
+
throw new Error(`Failed to commit changes: ${String(error)}`);
|
|
331
|
+
}
|
|
332
|
+
// Push to PR branch
|
|
333
|
+
this.logger.info({ prNumber, branch: branchName }, 'Pushing changes to PR branch');
|
|
334
|
+
try {
|
|
335
|
+
await github.push('origin', branchName);
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
this.logger.error({ error: String(error), prNumber, branch: branchName }, 'Failed to push changes to PR branch');
|
|
339
|
+
throw new Error(`Failed to push to branch ${branchName}: ${String(error)}`);
|
|
340
|
+
}
|
|
341
|
+
this.logger.info({ prNumber, branch: branchName }, 'Successfully pushed changes');
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Post a reply to each unresolved review thread.
|
|
346
|
+
*
|
|
347
|
+
* SC-006: Never resolves threads — that's left to the human reviewer.
|
|
348
|
+
* FR-007: Partial failure is acceptable: if some replies fail, log warnings but continue.
|
|
349
|
+
* The handler will still remove the label even if some replies fail.
|
|
350
|
+
*/
|
|
351
|
+
async replyToThreads(github, owner, repo, prNumber, comments) {
|
|
352
|
+
this.logger.info({ prNumber, threadCount: comments.length }, 'Posting replies to review threads');
|
|
353
|
+
let successCount = 0;
|
|
354
|
+
let failureCount = 0;
|
|
355
|
+
for (const comment of comments) {
|
|
356
|
+
try {
|
|
357
|
+
// SC-006: Only reply, never resolve — human reviewer will resolve after verification
|
|
358
|
+
const replyBody = `I've addressed this feedback in the latest commit. Please review the changes.`;
|
|
359
|
+
await github.replyToPRComment(owner, repo, prNumber, comment.id, replyBody);
|
|
360
|
+
this.logger.debug({ prNumber, commentId: comment.id, path: comment.path, line: comment.line }, 'Posted reply to review thread');
|
|
361
|
+
successCount++;
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
// FR-007: Continue processing other threads even if this one fails
|
|
365
|
+
failureCount++;
|
|
366
|
+
this.logger.warn({ error: String(error), prNumber, commentId: comment.id, path: comment.path, line: comment.line }, 'Failed to post reply to review thread — continuing with remaining threads');
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (failureCount > 0) {
|
|
370
|
+
this.logger.warn({ prNumber, successCount, failureCount, total: comments.length }, 'Some thread replies failed — partial success (acceptable per FR-007)');
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
this.logger.info({ prNumber, successCount, total: comments.length }, 'Successfully posted all thread replies');
|
|
374
|
+
}
|
|
375
|
+
// FR-007: Even if all replies fail, we don't throw — the caller will still remove the label
|
|
376
|
+
// because the code changes were pushed successfully
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Remove the `waiting-for:address-pr-feedback` label from the linked issue.
|
|
380
|
+
*/
|
|
381
|
+
async removeFeedbackLabel(github, owner, repo, issueNumber) {
|
|
382
|
+
try {
|
|
383
|
+
await github.removeLabels(owner, repo, issueNumber, ['waiting-for:address-pr-feedback']);
|
|
384
|
+
this.logger.info({ issueNumber }, 'Removed waiting-for:address-pr-feedback label');
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
this.logger.warn({ error: String(error), issueNumber }, 'Failed to remove waiting-for:address-pr-feedback label — non-fatal');
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
//# sourceMappingURL=pr-feedback-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pr-feedback-handler.js","sourceRoot":"","sources":["../../src/worker/pr-feedback-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { GitHubClient } from '@generacy-ai/workflow-engine';
|
|
2
|
+
import type { PrToIssueLink } from '../types/monitor.js';
|
|
3
|
+
import type { Logger } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Input subset of a PullRequest needed for linking.
|
|
6
|
+
* Avoids coupling to the full PullRequest type.
|
|
7
|
+
*/
|
|
8
|
+
export interface PrLinkInput {
|
|
9
|
+
number: number;
|
|
10
|
+
body: string;
|
|
11
|
+
head: {
|
|
12
|
+
ref: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Utility class that resolves the link between a pull request and its
|
|
17
|
+
* orchestrated issue.
|
|
18
|
+
*
|
|
19
|
+
* Resolution strategy (first match wins):
|
|
20
|
+
* 1. Parse closing keywords in the PR body (`Closes #42`, `Fixes #7`, etc.)
|
|
21
|
+
* 2. Parse the leading issue number from the branch name (`42-feature-name`)
|
|
22
|
+
*
|
|
23
|
+
* After a candidate issue number is found, the class verifies that the issue
|
|
24
|
+
* exists and carries an `agent:*` label (i.e. it is orchestrated). PRs that
|
|
25
|
+
* are not linked to an orchestrated issue return `null`.
|
|
26
|
+
*/
|
|
27
|
+
export declare class PrLinker {
|
|
28
|
+
private readonly logger;
|
|
29
|
+
/**
|
|
30
|
+
* Closing-keyword pattern (case-insensitive, word-boundary aware).
|
|
31
|
+
* Matches: close/closes/closed/fix/fixes/fixed/resolve/resolves/resolved #N
|
|
32
|
+
*
|
|
33
|
+
* NOTE: We use a function to return a fresh regex each time because
|
|
34
|
+
* RegExp objects with the `g` flag are stateful (they track `lastIndex`).
|
|
35
|
+
*/
|
|
36
|
+
private static closingRegex;
|
|
37
|
+
/**
|
|
38
|
+
* Branch-name pattern: leading digits followed by a hyphen.
|
|
39
|
+
* Matches: `42-feature-name` → 42
|
|
40
|
+
*/
|
|
41
|
+
private static readonly BRANCH_REGEX;
|
|
42
|
+
constructor(logger: Logger);
|
|
43
|
+
/**
|
|
44
|
+
* Extract the first referenced issue number from a PR body using
|
|
45
|
+
* GitHub closing keywords.
|
|
46
|
+
*
|
|
47
|
+
* @returns The issue number, or `null` if no keyword match is found.
|
|
48
|
+
*/
|
|
49
|
+
parsePrBody(body: string): number | null;
|
|
50
|
+
/**
|
|
51
|
+
* Extract an issue number from a branch name of the form `{N}-{description}`.
|
|
52
|
+
*
|
|
53
|
+
* @returns The issue number, or `null` if the branch doesn't match.
|
|
54
|
+
*/
|
|
55
|
+
parseBranchName(branch: string): number | null;
|
|
56
|
+
/**
|
|
57
|
+
* Attempt to link a PR to an orchestrated issue.
|
|
58
|
+
*
|
|
59
|
+
* Resolution order:
|
|
60
|
+
* 1. PR body closing keywords (e.g. `Fixes #42`)
|
|
61
|
+
* 2. Branch name prefix (e.g. `42-feature-name`)
|
|
62
|
+
*
|
|
63
|
+
* After resolving a candidate, the linked issue is fetched to verify it
|
|
64
|
+
* exists and carries an `agent:*` label. Returns `null` when no valid
|
|
65
|
+
* link can be established.
|
|
66
|
+
*/
|
|
67
|
+
linkPrToIssue(github: GitHubClient, owner: string, repo: string, pr: PrLinkInput): Promise<PrToIssueLink | null>;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=pr-linker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pr-linker.d.ts","sourceRoot":"","sources":["../../src/worker/pr-linker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;CACvB;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,QAAQ;IAkBP,OAAO,CAAC,QAAQ,CAAC,MAAM;IAjBnC;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,CAAC,YAAY;IAI3B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAa;gBAEpB,MAAM,EAAE,MAAM;IAE3C;;;;;OAKG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IASxC;;;;OAIG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAS9C;;;;;;;;;;OAUG;IACG,aAAa,CACjB,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,WAAW,GACd,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;CAqDjC"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility class that resolves the link between a pull request and its
|
|
3
|
+
* orchestrated issue.
|
|
4
|
+
*
|
|
5
|
+
* Resolution strategy (first match wins):
|
|
6
|
+
* 1. Parse closing keywords in the PR body (`Closes #42`, `Fixes #7`, etc.)
|
|
7
|
+
* 2. Parse the leading issue number from the branch name (`42-feature-name`)
|
|
8
|
+
*
|
|
9
|
+
* After a candidate issue number is found, the class verifies that the issue
|
|
10
|
+
* exists and carries an `agent:*` label (i.e. it is orchestrated). PRs that
|
|
11
|
+
* are not linked to an orchestrated issue return `null`.
|
|
12
|
+
*/
|
|
13
|
+
export class PrLinker {
|
|
14
|
+
logger;
|
|
15
|
+
/**
|
|
16
|
+
* Closing-keyword pattern (case-insensitive, word-boundary aware).
|
|
17
|
+
* Matches: close/closes/closed/fix/fixes/fixed/resolve/resolves/resolved #N
|
|
18
|
+
*
|
|
19
|
+
* NOTE: We use a function to return a fresh regex each time because
|
|
20
|
+
* RegExp objects with the `g` flag are stateful (they track `lastIndex`).
|
|
21
|
+
*/
|
|
22
|
+
static closingRegex() {
|
|
23
|
+
return /\b(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s#(\d+)/gi;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Branch-name pattern: leading digits followed by a hyphen.
|
|
27
|
+
* Matches: `42-feature-name` → 42
|
|
28
|
+
*/
|
|
29
|
+
static BRANCH_REGEX = /^(\d+)-/;
|
|
30
|
+
constructor(logger) {
|
|
31
|
+
this.logger = logger;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Extract the first referenced issue number from a PR body using
|
|
35
|
+
* GitHub closing keywords.
|
|
36
|
+
*
|
|
37
|
+
* @returns The issue number, or `null` if no keyword match is found.
|
|
38
|
+
*/
|
|
39
|
+
parsePrBody(body) {
|
|
40
|
+
if (!body)
|
|
41
|
+
return null;
|
|
42
|
+
const match = PrLinker.closingRegex().exec(body);
|
|
43
|
+
if (!match?.[1])
|
|
44
|
+
return null;
|
|
45
|
+
return parseInt(match[1], 10);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Extract an issue number from a branch name of the form `{N}-{description}`.
|
|
49
|
+
*
|
|
50
|
+
* @returns The issue number, or `null` if the branch doesn't match.
|
|
51
|
+
*/
|
|
52
|
+
parseBranchName(branch) {
|
|
53
|
+
if (!branch)
|
|
54
|
+
return null;
|
|
55
|
+
const match = PrLinker.BRANCH_REGEX.exec(branch);
|
|
56
|
+
if (!match?.[1])
|
|
57
|
+
return null;
|
|
58
|
+
return parseInt(match[1], 10);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Attempt to link a PR to an orchestrated issue.
|
|
62
|
+
*
|
|
63
|
+
* Resolution order:
|
|
64
|
+
* 1. PR body closing keywords (e.g. `Fixes #42`)
|
|
65
|
+
* 2. Branch name prefix (e.g. `42-feature-name`)
|
|
66
|
+
*
|
|
67
|
+
* After resolving a candidate, the linked issue is fetched to verify it
|
|
68
|
+
* exists and carries an `agent:*` label. Returns `null` when no valid
|
|
69
|
+
* link can be established.
|
|
70
|
+
*/
|
|
71
|
+
async linkPrToIssue(github, owner, repo, pr) {
|
|
72
|
+
// 1. Try PR body keywords first
|
|
73
|
+
let issueNumber = this.parsePrBody(pr.body);
|
|
74
|
+
let linkMethod = 'pr-body';
|
|
75
|
+
// 2. Fallback to branch name
|
|
76
|
+
if (issueNumber === null) {
|
|
77
|
+
issueNumber = this.parseBranchName(pr.head.ref);
|
|
78
|
+
linkMethod = 'branch-name';
|
|
79
|
+
}
|
|
80
|
+
if (issueNumber === null) {
|
|
81
|
+
this.logger.debug({ prNumber: pr.number, owner, repo }, 'No issue link found in PR body or branch name');
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
// 3. Verify the issue exists and is orchestrated (has agent:* label)
|
|
85
|
+
let issueAssignees;
|
|
86
|
+
try {
|
|
87
|
+
const issue = await github.getIssue(owner, repo, issueNumber);
|
|
88
|
+
const isOrchestrated = issue.labels.some((l) => l.name.startsWith('agent:'));
|
|
89
|
+
issueAssignees = issue.assignees;
|
|
90
|
+
if (!isOrchestrated) {
|
|
91
|
+
this.logger.debug({ prNumber: pr.number, issueNumber, owner, repo }, 'Linked issue does not have an agent:* label — skipping non-orchestrated issue');
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
this.logger.warn({ prNumber: pr.number, issueNumber, owner, repo, err: error }, 'Failed to fetch linked issue — it may not exist');
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
this.logger.info({ prNumber: pr.number, issueNumber, linkMethod, owner, repo }, `Linked PR #${pr.number} to issue #${issueNumber} via ${linkMethod}`);
|
|
100
|
+
return {
|
|
101
|
+
prNumber: pr.number,
|
|
102
|
+
issueNumber,
|
|
103
|
+
linkMethod,
|
|
104
|
+
assignees: issueAssignees,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=pr-linker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pr-linker.js","sourceRoot":"","sources":["../../src/worker/pr-linker.ts"],"names":[],"mappings":"AAcA;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,QAAQ;IAkBU;IAjB7B;;;;;;OAMG;IACK,MAAM,CAAC,YAAY;QACzB,OAAO,+EAA+E,CAAC;IACzF,CAAC;IAED;;;OAGG;IACK,MAAM,CAAU,YAAY,GAAG,SAAS,CAAC;IAEjD,YAA6B,MAAc;QAAd,WAAM,GAAN,MAAM,CAAQ;IAAG,CAAC;IAE/C;;;;;OAKG;IACH,WAAW,CAAC,IAAY;QACtB,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QAEvB,MAAM,KAAK,GAAG,QAAQ,CAAC,YAAY,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjD,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QAE7B,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChC,CAAC;IAED;;;;OAIG;IACH,eAAe,CAAC,MAAc;QAC5B,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QAEzB,MAAM,KAAK,GAAG,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACjD,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QAE7B,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChC,CAAC;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,aAAa,CACjB,MAAoB,EACpB,KAAa,EACb,IAAY,EACZ,EAAe;QAEf,gCAAgC;QAChC,IAAI,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,UAAU,GAAgC,SAAS,CAAC;QAExD,6BAA6B;QAC7B,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;YACzB,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAChD,UAAU,GAAG,aAAa,CAAC;QAC7B,CAAC;QAED,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;YACzB,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,EAAE,QAAQ,EAAE,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,EACpC,+CAA+C,CAChD,CAAC;YACF,OAAO,IAAI,CAAC;QACd,CAAC;QAED,qEAAqE;QACrE,IAAI,cAAwB,CAAC;QAC7B,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;YAC9D,MAAM,cAAc,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC;YAC7E,cAAc,GAAG,KAAK,CAAC,SAAS,CAAC;YAEjC,IAAI,CAAC,cAAc,EAAE,CAAC;gBACpB,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,EAAE,QAAQ,EAAE,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,EACjD,+EAA+E,CAChF,CAAC;gBACF,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,EAAE,QAAQ,EAAE,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,EAC7D,iDAAiD,CAClD,CAAC;YACF,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,EAAE,QAAQ,EAAE,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,EAC7D,cAAc,EAAE,CAAC,MAAM,cAAc,WAAW,QAAQ,UAAU,EAAE,CACrE,CAAC;QAEF,OAAO;YACL,QAAQ,EAAE,EAAE,CAAC,MAAM;YACnB,WAAW;YACX,UAAU;YACV,SAAS,EAAE,cAAc;SAC1B,CAAC;IACJ,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { GitHubClient } from '@generacy-ai/workflow-engine';
|
|
2
|
+
import type { WorkflowPhase, Logger } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Manages draft PR creation and git commit/push operations between workflow phases.
|
|
5
|
+
*
|
|
6
|
+
* After each phase completes:
|
|
7
|
+
* 1. Commits any changed files with a phase-specific message
|
|
8
|
+
* 2. Pushes the branch to the remote
|
|
9
|
+
* 3. Creates a draft PR (if one doesn't already exist)
|
|
10
|
+
*
|
|
11
|
+
* This ensures the PR is created early (after specify) and updated incrementally.
|
|
12
|
+
*/
|
|
13
|
+
export declare class PrManager {
|
|
14
|
+
private readonly github;
|
|
15
|
+
private readonly owner;
|
|
16
|
+
private readonly repo;
|
|
17
|
+
private readonly issueNumber;
|
|
18
|
+
private readonly logger;
|
|
19
|
+
private prUrl;
|
|
20
|
+
private prNumber;
|
|
21
|
+
constructor(github: GitHubClient, owner: string, repo: string, issueNumber: number, logger: Logger);
|
|
22
|
+
/**
|
|
23
|
+
* Get the current PR URL (if a PR has been created).
|
|
24
|
+
*/
|
|
25
|
+
getPrUrl(): string | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Commit any changes, push to remote, and ensure a draft PR exists.
|
|
28
|
+
*
|
|
29
|
+
* Safe to call after every phase — handles "nothing to commit" and
|
|
30
|
+
* "PR already exists" gracefully.
|
|
31
|
+
*
|
|
32
|
+
* @returns The PR URL if one exists/was created, undefined otherwise.
|
|
33
|
+
*/
|
|
34
|
+
commitPushAndEnsurePr(phase: WorkflowPhase): Promise<string | undefined>;
|
|
35
|
+
/**
|
|
36
|
+
* Commit all changed files and push to the remote.
|
|
37
|
+
*
|
|
38
|
+
* Handles "nothing to commit" gracefully by checking git status first.
|
|
39
|
+
*/
|
|
40
|
+
private commitAndPush;
|
|
41
|
+
/**
|
|
42
|
+
* Ensure a draft PR exists for the current branch.
|
|
43
|
+
*
|
|
44
|
+
* On first call: creates a new draft PR linked to the issue.
|
|
45
|
+
* On subsequent calls: returns the existing PR URL (no-op).
|
|
46
|
+
*
|
|
47
|
+
* @returns The PR URL, or undefined if creation failed.
|
|
48
|
+
*/
|
|
49
|
+
private ensureDraftPr;
|
|
50
|
+
/**
|
|
51
|
+
* Mark the draft PR as ready for review.
|
|
52
|
+
*
|
|
53
|
+
* Should be called after the workflow completes successfully (all phases done).
|
|
54
|
+
* If no PR exists or the PR number is unknown, this is a no-op.
|
|
55
|
+
*
|
|
56
|
+
* The underlying GitHub API call is idempotent — calling it on a non-draft PR
|
|
57
|
+
* has no effect.
|
|
58
|
+
*/
|
|
59
|
+
markReadyForReview(): Promise<void>;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=pr-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pr-manager.d.ts","sourceRoot":"","sources":["../../src/worker/pr-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAExD;;;;;;;;;GASG;AACH,qBAAa,SAAS;IAKlB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IARzB,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,QAAQ,CAAqB;gBAGlB,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM;IAGjC;;OAEG;IACH,QAAQ,IAAI,MAAM,GAAG,SAAS;IAI9B;;;;;;;OAOG;IACG,qBAAqB,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAK9E;;;;OAIG;YACW,aAAa;IAoC3B;;;;;;;OAOG;YACW,aAAa;IAkD3B;;;;;;;;OAQG;IACG,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;CAoB1C"}
|