@generacy-ai/orchestrator 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/schema.d.ts +7 -7
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/routes/health.d.ts.map +1 -1
- package/dist/routes/health.js +5 -0
- package/dist/routes/health.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +130 -26
- package/dist/server.js.map +1 -1
- package/dist/services/base-advance-monitor-service.d.ts +78 -0
- package/dist/services/base-advance-monitor-service.d.ts.map +1 -0
- package/dist/services/base-advance-monitor-service.js +233 -0
- package/dist/services/base-advance-monitor-service.js.map +1 -0
- package/dist/services/boot-resume-service.d.ts +19 -0
- package/dist/services/boot-resume-service.d.ts.map +1 -0
- package/dist/services/boot-resume-service.js +87 -0
- package/dist/services/boot-resume-service.js.map +1 -0
- package/dist/services/epic-completion-monitor-service.d.ts.map +1 -1
- package/dist/services/epic-completion-monitor-service.js +2 -0
- package/dist/services/epic-completion-monitor-service.js.map +1 -1
- package/dist/services/in-memory-queue-adapter.d.ts +4 -0
- package/dist/services/in-memory-queue-adapter.d.ts.map +1 -1
- package/dist/services/in-memory-queue-adapter.js +30 -3
- package/dist/services/in-memory-queue-adapter.js.map +1 -1
- package/dist/services/label-monitor-service.d.ts +3 -3
- package/dist/services/label-monitor-service.d.ts.map +1 -1
- package/dist/services/label-monitor-service.js +58 -41
- package/dist/services/label-monitor-service.js.map +1 -1
- package/dist/services/label-sync-service.d.ts.map +1 -1
- package/dist/services/label-sync-service.js +45 -10
- package/dist/services/label-sync-service.js.map +1 -1
- package/dist/services/merge-conflict-monitor-service.d.ts +73 -0
- package/dist/services/merge-conflict-monitor-service.d.ts.map +1 -0
- package/dist/services/merge-conflict-monitor-service.js +295 -0
- package/dist/services/merge-conflict-monitor-service.js.map +1 -0
- package/dist/services/orchestrator-version.d.ts +2 -0
- package/dist/services/orchestrator-version.d.ts.map +1 -0
- package/dist/services/orchestrator-version.js +23 -0
- package/dist/services/orchestrator-version.js.map +1 -0
- package/dist/services/phase-tracker-service.d.ts +10 -0
- package/dist/services/phase-tracker-service.d.ts.map +1 -1
- package/dist/services/phase-tracker-service.js +38 -0
- package/dist/services/phase-tracker-service.js.map +1 -1
- package/dist/services/post-activation-dispatch.d.ts +18 -0
- package/dist/services/post-activation-dispatch.d.ts.map +1 -0
- package/dist/services/post-activation-dispatch.js +30 -0
- package/dist/services/post-activation-dispatch.js.map +1 -0
- package/dist/services/pr-feedback-monitor-service.d.ts +13 -4
- package/dist/services/pr-feedback-monitor-service.d.ts.map +1 -1
- package/dist/services/pr-feedback-monitor-service.js +212 -26
- package/dist/services/pr-feedback-monitor-service.js.map +1 -1
- package/dist/services/redis-queue-adapter.d.ts +4 -0
- package/dist/services/redis-queue-adapter.d.ts.map +1 -1
- package/dist/services/redis-queue-adapter.js +90 -10
- package/dist/services/redis-queue-adapter.js.map +1 -1
- package/dist/services/worker-dispatcher.d.ts +14 -2
- package/dist/services/worker-dispatcher.d.ts.map +1 -1
- package/dist/services/worker-dispatcher.js +55 -2
- package/dist/services/worker-dispatcher.js.map +1 -1
- package/dist/types/api.d.ts +3 -0
- package/dist/types/api.d.ts.map +1 -1
- package/dist/types/api.js +1 -0
- package/dist/types/api.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/monitor.d.ts +72 -4
- package/dist/types/monitor.d.ts.map +1 -1
- package/dist/worker/base-merge.d.ts +54 -0
- package/dist/worker/base-merge.d.ts.map +1 -0
- package/dist/worker/base-merge.js +99 -0
- package/dist/worker/base-merge.js.map +1 -0
- package/dist/worker/clarification-markers.d.ts +29 -0
- package/dist/worker/clarification-markers.d.ts.map +1 -0
- package/dist/worker/clarification-markers.js +44 -0
- package/dist/worker/clarification-markers.js.map +1 -0
- package/dist/worker/clarification-poster.d.ts.map +1 -1
- package/dist/worker/clarification-poster.js +266 -25
- package/dist/worker/clarification-poster.js.map +1 -1
- package/dist/worker/claude-cli-worker.d.ts +21 -2
- package/dist/worker/claude-cli-worker.d.ts.map +1 -1
- package/dist/worker/claude-cli-worker.js +149 -4
- package/dist/worker/claude-cli-worker.js.map +1 -1
- package/dist/worker/cli-spawner.d.ts +4 -0
- package/dist/worker/cli-spawner.d.ts.map +1 -1
- package/dist/worker/cli-spawner.js +51 -7
- package/dist/worker/cli-spawner.js.map +1 -1
- package/dist/worker/config.d.ts +24 -8
- package/dist/worker/config.d.ts.map +1 -1
- package/dist/worker/config.js +37 -2
- package/dist/worker/config.js.map +1 -1
- package/dist/worker/evidence-hash.d.ts +32 -0
- package/dist/worker/evidence-hash.d.ts.map +1 -0
- package/dist/worker/evidence-hash.js +139 -0
- package/dist/worker/evidence-hash.js.map +1 -0
- package/dist/worker/handler-outcome-assertion.d.ts +38 -0
- package/dist/worker/handler-outcome-assertion.d.ts.map +1 -0
- package/dist/worker/handler-outcome-assertion.js +58 -0
- package/dist/worker/handler-outcome-assertion.js.map +1 -0
- package/dist/worker/handler-outcome.d.ts +50 -0
- package/dist/worker/handler-outcome.d.ts.map +1 -0
- package/dist/worker/handler-outcome.js +2 -0
- package/dist/worker/handler-outcome.js.map +1 -0
- package/dist/worker/label-manager.d.ts +58 -2
- package/dist/worker/label-manager.d.ts.map +1 -1
- package/dist/worker/label-manager.js +225 -27
- package/dist/worker/label-manager.js.map +1 -1
- package/dist/worker/merge-conflict-handler.d.ts +141 -0
- package/dist/worker/merge-conflict-handler.d.ts.map +1 -0
- package/dist/worker/merge-conflict-handler.js +642 -0
- package/dist/worker/merge-conflict-handler.js.map +1 -0
- package/dist/worker/merge-conflict-prompt.d.ts +35 -0
- package/dist/worker/merge-conflict-prompt.d.ts.map +1 -0
- package/dist/worker/merge-conflict-prompt.js +71 -0
- package/dist/worker/merge-conflict-prompt.js.map +1 -0
- package/dist/worker/merge-conflict-remedy.d.ts +42 -0
- package/dist/worker/merge-conflict-remedy.d.ts.map +1 -0
- package/dist/worker/merge-conflict-remedy.js +28 -0
- package/dist/worker/merge-conflict-remedy.js.map +1 -0
- package/dist/worker/output-tail-synthesis.d.ts +15 -0
- package/dist/worker/output-tail-synthesis.d.ts.map +1 -0
- package/dist/worker/output-tail-synthesis.js +25 -0
- package/dist/worker/output-tail-synthesis.js.map +1 -0
- package/dist/worker/output-tail.d.ts +13 -0
- package/dist/worker/output-tail.d.ts.map +1 -0
- package/dist/worker/output-tail.js +31 -0
- package/dist/worker/output-tail.js.map +1 -0
- package/dist/worker/pause-context.d.ts +30 -0
- package/dist/worker/pause-context.d.ts.map +1 -0
- package/dist/worker/pause-context.js +98 -0
- package/dist/worker/pause-context.js.map +1 -0
- package/dist/worker/phase-loop.d.ts +66 -0
- package/dist/worker/phase-loop.d.ts.map +1 -1
- package/dist/worker/phase-loop.js +295 -29
- package/dist/worker/phase-loop.js.map +1 -1
- package/dist/worker/pr-feedback-handler.d.ts +46 -46
- package/dist/worker/pr-feedback-handler.d.ts.map +1 -1
- package/dist/worker/pr-feedback-handler.js +276 -105
- package/dist/worker/pr-feedback-handler.js.map +1 -1
- package/dist/worker/pr-manager.d.ts +5 -0
- package/dist/worker/pr-manager.d.ts.map +1 -1
- package/dist/worker/pr-manager.js +7 -0
- package/dist/worker/pr-manager.js.map +1 -1
- package/dist/worker/product-diff.d.ts +39 -0
- package/dist/worker/product-diff.d.ts.map +1 -0
- package/dist/worker/product-diff.js +43 -0
- package/dist/worker/product-diff.js.map +1 -0
- package/dist/worker/provisioning-failure.d.ts +14 -0
- package/dist/worker/provisioning-failure.d.ts.map +1 -0
- package/dist/worker/provisioning-failure.js +2 -0
- package/dist/worker/provisioning-failure.js.map +1 -0
- package/dist/worker/repo-checkout.d.ts +14 -0
- package/dist/worker/repo-checkout.d.ts.map +1 -1
- package/dist/worker/repo-checkout.js +19 -1
- package/dist/worker/repo-checkout.js.map +1 -1
- package/dist/worker/stage-comment-manager.d.ts +42 -1
- package/dist/worker/stage-comment-manager.d.ts.map +1 -1
- package/dist/worker/stage-comment-manager.js +205 -1
- package/dist/worker/stage-comment-manager.js.map +1 -1
- package/dist/worker/terminal-label-op-error.d.ts +23 -0
- package/dist/worker/terminal-label-op-error.d.ts.map +1 -0
- package/dist/worker/terminal-label-op-error.js +27 -0
- package/dist/worker/terminal-label-op-error.js.map +1 -0
- package/dist/worker/types.d.ts +161 -2
- package/dist/worker/types.d.ts.map +1 -1
- package/dist/worker/types.js +11 -0
- package/dist/worker/types.js.map +1 -1
- package/dist/worker/validate-fix-handler.d.ts +79 -0
- package/dist/worker/validate-fix-handler.d.ts.map +1 -0
- package/dist/worker/validate-fix-handler.js +320 -0
- package/dist/worker/validate-fix-handler.js.map +1 -0
- package/dist/worker/worker-result.d.ts +39 -0
- package/dist/worker/worker-result.d.ts.map +1 -0
- package/dist/worker/worker-result.js +10 -0
- package/dist/worker/worker-result.js.map +1 -0
- package/package.json +6 -5
|
@@ -10,48 +10,22 @@ import type { AgentLauncher } from '../launcher/agent-launcher.js';
|
|
|
10
10
|
* 1. Extract PR number from queue item metadata
|
|
11
11
|
* 2. Fetch the PR to get the branch name
|
|
12
12
|
* 3. Switch to the PR branch (not default branch)
|
|
13
|
-
* 4. Fetch fresh unresolved review threads
|
|
14
|
-
* 5. Build a structured prompt with all unresolved comments
|
|
13
|
+
* 4. Fetch fresh unresolved review threads (trust-filtered per-thread)
|
|
14
|
+
* 5. Build a structured prompt with all trusted unresolved comments
|
|
15
15
|
* 6. Spawn Claude CLI to address the feedback
|
|
16
|
-
* 7.
|
|
17
|
-
* 8.
|
|
18
|
-
*
|
|
16
|
+
* 7. Commit + push. If CLI failed OR no diff → Disposition B (blocked)
|
|
17
|
+
* 8. Otherwise: for each trusted unresolved thread — post one reply targeting
|
|
18
|
+
* the root comment, then call `resolveReviewThread(thread.id)`
|
|
19
|
+
* 9. Strict-decrease success test — R = count of resolves that succeeded.
|
|
20
|
+
* R === 0 → Disposition B (blocked); R ≥ 1 → Disposition A (success):
|
|
21
|
+
* warn once per persistently-failed thread, then remove the
|
|
22
|
+
* `waiting-for:address-pr-feedback` label
|
|
19
23
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* - Label retention ensures the task will be retried on next detection cycle
|
|
26
|
-
* - Log timeout warning with structured context
|
|
27
|
-
*
|
|
28
|
-
* **FR-007: Reply failure handling**
|
|
29
|
-
* - If posting replies fails for some threads: still remove label
|
|
30
|
-
* - Log warnings for each failed reply
|
|
31
|
-
* - Partial reply success is acceptable (code changes were pushed successfully)
|
|
32
|
-
* - Handler does not throw on reply failures
|
|
33
|
-
*
|
|
34
|
-
* **SC-006: Thread resolution prevention**
|
|
35
|
-
* - Only use `replyToPRComment()` API — never call any thread-resolve API
|
|
36
|
-
* - Human reviewer will resolve threads after verifying changes
|
|
37
|
-
* - Comments in code explicitly reference SC-006 for clarity
|
|
38
|
-
*
|
|
39
|
-
* **Structured logging**
|
|
40
|
-
* - All operations logged with structured context (prNumber, issueNumber, owner, repo)
|
|
41
|
-
* - Error logs include full error messages and relevant context
|
|
42
|
-
* - Success/failure states clearly logged for observability
|
|
43
|
-
* - All FR/SC references included in log messages for traceability
|
|
44
|
-
*
|
|
45
|
-
* **Critical operation failures**
|
|
46
|
-
* - PR fetch failure: throw (cannot proceed without PR details)
|
|
47
|
-
* - Branch switch failure: throw (wrong branch would cause issues)
|
|
48
|
-
* - Comment fetch failure: throw (cannot address unknown feedback)
|
|
49
|
-
* - Push failure: log error but don't throw (allows retry via label)
|
|
50
|
-
*
|
|
51
|
-
* **Non-critical operation failures**
|
|
52
|
-
* - No unresolved threads: remove label, early return (success case)
|
|
53
|
-
* - Reply posting: log warning, continue (FR-007)
|
|
54
|
-
* - Label removal: log warning, continue (non-fatal)
|
|
24
|
+
* Disposition B (blocked, #883): add `blocked:stuck-feedback-loop` and leave
|
|
25
|
+
* `waiting-for:address-pr-feedback` in place. The monitor's pre-enqueue
|
|
26
|
+
* `blocked:*` check keeps the loop paused until an operator removes the
|
|
27
|
+
* label. This ends the runaway "reply-only" cycle (5→10→20→…) observed on
|
|
28
|
+
* christrudelpw/sniplink#4.
|
|
55
29
|
*/
|
|
56
30
|
export declare class PrFeedbackHandler {
|
|
57
31
|
private readonly config;
|
|
@@ -91,16 +65,42 @@ export declare class PrFeedbackHandler {
|
|
|
91
65
|
*/
|
|
92
66
|
private commitAndPushChanges;
|
|
93
67
|
/**
|
|
94
|
-
* Post a reply
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* FR-007: Partial failure is acceptable: if some replies fail, log warnings but continue.
|
|
98
|
-
* The handler will still remove the label even if some replies fail.
|
|
68
|
+
* Post a single reply targeting the root comment of a review thread.
|
|
69
|
+
* Returns a discriminated result — failures do not throw so the caller
|
|
70
|
+
* (#883 per-thread outcome loop) can aggregate results.
|
|
99
71
|
*/
|
|
100
|
-
private
|
|
72
|
+
private tryPostReply;
|
|
73
|
+
/**
|
|
74
|
+
* Delegate to `github.resolveReviewThread` (which owns the 3× retry). Wraps
|
|
75
|
+
* the result in a discriminated union so the caller can aggregate outcomes
|
|
76
|
+
* without a try/catch (#883).
|
|
77
|
+
*/
|
|
78
|
+
private tryResolveReviewThread;
|
|
79
|
+
/**
|
|
80
|
+
* Read the short SHA of the just-pushed HEAD commit. Returns null when the
|
|
81
|
+
* git command fails; caller falls back to `<unknown>` in the reply body —
|
|
82
|
+
* the SHA is decoration, not termination logic (#883).
|
|
83
|
+
*/
|
|
84
|
+
private getHeadShortSha;
|
|
85
|
+
/**
|
|
86
|
+
* Add the `blocked:stuck-feedback-loop` label to signal that the fix cycle
|
|
87
|
+
* cannot advance and must not be re-enqueued until the operator removes the
|
|
88
|
+
* label. Non-fatal on failure — leaving `waiting-for:*` in place is the
|
|
89
|
+
* fallback safety net (#883).
|
|
90
|
+
*/
|
|
91
|
+
private addBlockedStuckFeedbackLoopLabel;
|
|
101
92
|
/**
|
|
102
93
|
* Remove the `waiting-for:address-pr-feedback` label from the linked issue.
|
|
103
94
|
*/
|
|
104
95
|
private removeFeedbackLabel;
|
|
96
|
+
/**
|
|
97
|
+
* Structural clear of `agent:in-progress` on the linked issue (#926 SC-004).
|
|
98
|
+
* Called from the shared `finally` block in `handle()` — runs on every
|
|
99
|
+
* terminal exit path (Cases A/B, both blocked-stuck dispositions, happy
|
|
100
|
+
* path, and thrown errors). Idempotent: GitHub's `removeLabels` is a no-op
|
|
101
|
+
* when the label is already absent, so the happy-path coalesced removal +
|
|
102
|
+
* this `finally` clear together produce at most one truthful post-state.
|
|
103
|
+
*/
|
|
104
|
+
private clearInProgressLabel;
|
|
105
105
|
}
|
|
106
106
|
//# sourceMappingURL=pr-feedback-handler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pr-feedback-handler.d.ts","sourceRoot":"","sources":["../../src/worker/pr-feedback-handler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pr-feedback-handler.d.ts","sourceRoot":"","sources":["../../src/worker/pr-feedback-handler.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,SAAS,EAAsB,MAAM,mBAAmB,CAAC;AACvE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AA6BnE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,iBAAiB;IAI1B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAN9B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;gBAGzB,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,aAAa,EAC5B,UAAU,CAAC,EAAE,eAAe,YAAA;IAK/C;;;;;OAKG;IACG,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA6UlE;;;;;;;;OAQG;IACH,OAAO,CAAC,mBAAmB;IAgC3B;;;;;OAKG;YACW,sBAAsB;IA0HpC;;;;OAIG;YACW,oBAAoB;IA2ElC;;;;OAIG;YACW,YAAY;IAgB1B;;;;OAIG;YACW,sBAAsB;IAYpC;;;;OAIG;YACW,eAAe;IAe7B;;;;;OAKG;YACW,gCAAgC;IAoB9C;;OAEG;YACW,mBAAmB;IAiBjC;;;;;;;OAOG;YACW,oBAAoB;CAenC"}
|
|
@@ -1,7 +1,18 @@
|
|
|
1
|
-
import { createGitHubClient } from '@generacy-ai/workflow-engine';
|
|
1
|
+
import { createGitHubClient, executeCommand, isTrustedCommentAuthor, tryLoadCommentTrustConfig, wrapUntrustedData, } from '@generacy-ai/workflow-engine';
|
|
2
2
|
import { OutputCapture } from './output-capture.js';
|
|
3
3
|
import { RepoCheckout } from './repo-checkout.js';
|
|
4
4
|
import { buildLaunchCredentials } from './credentials-helper.js';
|
|
5
|
+
/** Label added by the handler when the fix cycle cannot advance (#883). */
|
|
6
|
+
const BLOCKED_STUCK_FEEDBACK_LOOP_LABEL = 'blocked:stuck-feedback-loop';
|
|
7
|
+
/**
|
|
8
|
+
* `agent:in-progress` label — cleared structurally at the single shared exit
|
|
9
|
+
* path (#926 SC-004/SC-005). Extracted to a module constant so the literal
|
|
10
|
+
* appears at exactly one code site: the coalesced happy-path removal and the
|
|
11
|
+
* `clearInProgressLabel` fallback both reference this constant.
|
|
12
|
+
*/
|
|
13
|
+
const AGENT_IN_PROGRESS_LABEL = 'agent:in-progress';
|
|
14
|
+
/** Waiting gate cleared alongside `agent:in-progress` on the happy path. */
|
|
15
|
+
const WAITING_FOR_ADDRESS_PR_FEEDBACK_LABEL = 'waiting-for:address-pr-feedback';
|
|
5
16
|
/**
|
|
6
17
|
* Handles the `address-pr-feedback` command.
|
|
7
18
|
*
|
|
@@ -9,48 +20,22 @@ import { buildLaunchCredentials } from './credentials-helper.js';
|
|
|
9
20
|
* 1. Extract PR number from queue item metadata
|
|
10
21
|
* 2. Fetch the PR to get the branch name
|
|
11
22
|
* 3. Switch to the PR branch (not default branch)
|
|
12
|
-
* 4. Fetch fresh unresolved review threads
|
|
13
|
-
* 5. Build a structured prompt with all unresolved comments
|
|
23
|
+
* 4. Fetch fresh unresolved review threads (trust-filtered per-thread)
|
|
24
|
+
* 5. Build a structured prompt with all trusted unresolved comments
|
|
14
25
|
* 6. Spawn Claude CLI to address the feedback
|
|
15
|
-
* 7.
|
|
16
|
-
* 8.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* - If CLI times out: push any partial changes that were made
|
|
23
|
-
* - Keep `waiting-for:address-pr-feedback` label (don't remove)
|
|
24
|
-
* - Label retention ensures the task will be retried on next detection cycle
|
|
25
|
-
* - Log timeout warning with structured context
|
|
26
|
-
*
|
|
27
|
-
* **FR-007: Reply failure handling**
|
|
28
|
-
* - If posting replies fails for some threads: still remove label
|
|
29
|
-
* - Log warnings for each failed reply
|
|
30
|
-
* - Partial reply success is acceptable (code changes were pushed successfully)
|
|
31
|
-
* - Handler does not throw on reply failures
|
|
32
|
-
*
|
|
33
|
-
* **SC-006: Thread resolution prevention**
|
|
34
|
-
* - Only use `replyToPRComment()` API — never call any thread-resolve API
|
|
35
|
-
* - Human reviewer will resolve threads after verifying changes
|
|
36
|
-
* - Comments in code explicitly reference SC-006 for clarity
|
|
37
|
-
*
|
|
38
|
-
* **Structured logging**
|
|
39
|
-
* - All operations logged with structured context (prNumber, issueNumber, owner, repo)
|
|
40
|
-
* - Error logs include full error messages and relevant context
|
|
41
|
-
* - Success/failure states clearly logged for observability
|
|
42
|
-
* - All FR/SC references included in log messages for traceability
|
|
43
|
-
*
|
|
44
|
-
* **Critical operation failures**
|
|
45
|
-
* - PR fetch failure: throw (cannot proceed without PR details)
|
|
46
|
-
* - Branch switch failure: throw (wrong branch would cause issues)
|
|
47
|
-
* - Comment fetch failure: throw (cannot address unknown feedback)
|
|
48
|
-
* - Push failure: log error but don't throw (allows retry via label)
|
|
26
|
+
* 7. Commit + push. If CLI failed OR no diff → Disposition B (blocked)
|
|
27
|
+
* 8. Otherwise: for each trusted unresolved thread — post one reply targeting
|
|
28
|
+
* the root comment, then call `resolveReviewThread(thread.id)`
|
|
29
|
+
* 9. Strict-decrease success test — R = count of resolves that succeeded.
|
|
30
|
+
* R === 0 → Disposition B (blocked); R ≥ 1 → Disposition A (success):
|
|
31
|
+
* warn once per persistently-failed thread, then remove the
|
|
32
|
+
* `waiting-for:address-pr-feedback` label
|
|
49
33
|
*
|
|
50
|
-
*
|
|
51
|
-
* -
|
|
52
|
-
*
|
|
53
|
-
*
|
|
34
|
+
* Disposition B (blocked, #883): add `blocked:stuck-feedback-loop` and leave
|
|
35
|
+
* `waiting-for:address-pr-feedback` in place. The monitor's pre-enqueue
|
|
36
|
+
* `blocked:*` check keeps the loop paused until an operator removes the
|
|
37
|
+
* label. This ends the runaway "reply-only" cycle (5→10→20→…) observed on
|
|
38
|
+
* christrudelpw/sniplink#4.
|
|
54
39
|
*/
|
|
55
40
|
export class PrFeedbackHandler {
|
|
56
41
|
config;
|
|
@@ -80,8 +65,15 @@ export class PrFeedbackHandler {
|
|
|
80
65
|
const { prNumber } = metadata;
|
|
81
66
|
const workflowId = `${owner}/${repo}#${issueNumber}`;
|
|
82
67
|
this.logger.info({ prNumber, issueNumber, owner, repo }, 'Starting PR feedback addressing');
|
|
83
|
-
// Create GitHub client scoped to checkout path
|
|
68
|
+
// Create GitHub client scoped to checkout path. Hoisted above the try so
|
|
69
|
+
// the `finally` clear can call it on every exit path (#926 SC-004).
|
|
84
70
|
const github = createGitHubClient(checkoutPath);
|
|
71
|
+
// #926 SC-004: `agent:in-progress` is cleared structurally at a single
|
|
72
|
+
// shared exit path so no terminal return can leave the label pinned.
|
|
73
|
+
// Idempotency-safe: happy path already coalesces the clear into its own
|
|
74
|
+
// `removeLabels` call; this `finally` is a backstop for the four other
|
|
75
|
+
// exit paths (Cases A/B, both blocked-stuck dispositions, and thrown
|
|
76
|
+
// errors). Non-fatal on failure — mirrors `removeFeedbackLabel` shape.
|
|
85
77
|
try {
|
|
86
78
|
// 1. Fetch the PR to get branch name
|
|
87
79
|
let pr;
|
|
@@ -102,24 +94,109 @@ export class PrFeedbackHandler {
|
|
|
102
94
|
this.logger.error({ error: String(error), prNumber, branchName, checkoutPath }, 'Failed to switch to PR branch');
|
|
103
95
|
throw new Error(`Failed to switch to branch ${branchName}: ${String(error)}`);
|
|
104
96
|
}
|
|
105
|
-
// 3. Fetch fresh unresolved review threads
|
|
97
|
+
// 3. Fetch fresh unresolved review threads via GraphQL (#861).
|
|
98
|
+
// REST never populated `.resolved`, so the old filter always emitted []
|
|
99
|
+
// and the handler no-op'd. GraphQL exposes thread-level resolution.
|
|
106
100
|
let allComments;
|
|
101
|
+
let unresolvedThreadCount;
|
|
107
102
|
let unresolvedComments;
|
|
103
|
+
let trustedUnresolvedThreads;
|
|
104
|
+
let untrustedSkips;
|
|
108
105
|
try {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
106
|
+
const threads = await github.getPRReviewThreads(owner, repo, prNumber);
|
|
107
|
+
const unresolvedThreads = threads.filter(t => !t.isResolved);
|
|
108
|
+
allComments = threads.flatMap(t => t.comments);
|
|
109
|
+
unresolvedThreadCount = unresolvedThreads.length;
|
|
110
|
+
// Author-trust gating (#842, #869, #878). Log each skip and drop
|
|
111
|
+
// untrusted comments before the CLI ever sees them. `pr-feedback`
|
|
112
|
+
// surface honors widen-config from .agency/comment-trust.yaml. #878:
|
|
113
|
+
// self-authorship comes from GraphQL's `viewerDidAuthor` field on
|
|
114
|
+
// each comment, not a login-comparison rule threaded via context.
|
|
115
|
+
//
|
|
116
|
+
// #883: also track trusted-unresolved threads (not just comments) so
|
|
117
|
+
// the post-CLI batch can iterate one-reply-per-thread and call
|
|
118
|
+
// `resolveReviewThread` per thread.
|
|
119
|
+
const trustConfig = tryLoadCommentTrustConfig(checkoutPath, this.logger);
|
|
120
|
+
const botLogin = process.env['CLUSTER_GITHUB_USERNAME'] ?? process.env['GH_USERNAME'];
|
|
121
|
+
const trustedUnresolved = [];
|
|
122
|
+
const trustedThreads = [];
|
|
123
|
+
const skips = [];
|
|
124
|
+
for (const thread of unresolvedThreads) {
|
|
125
|
+
let threadHasTrusted = false;
|
|
126
|
+
for (const c of thread.comments) {
|
|
127
|
+
const decision = isTrustedCommentAuthor(c, 'pr-feedback', {
|
|
128
|
+
logger: this.logger,
|
|
129
|
+
...(botLogin ? { botLogin } : {}),
|
|
130
|
+
...(trustConfig ? { config: trustConfig } : {}),
|
|
131
|
+
});
|
|
132
|
+
if (decision.trusted) {
|
|
133
|
+
trustedUnresolved.push(c);
|
|
134
|
+
threadHasTrusted = true;
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
skips.push({
|
|
138
|
+
commentId: c.id,
|
|
139
|
+
author: c.author,
|
|
140
|
+
authorAssociation: c.authorAssociation,
|
|
141
|
+
reason: decision.reason,
|
|
142
|
+
viewerDidAuthor: c.viewerDidAuthor,
|
|
143
|
+
});
|
|
144
|
+
this.logger.info({
|
|
145
|
+
event: 'comment-skipped',
|
|
146
|
+
surface: 'pr-feedback',
|
|
147
|
+
commentId: c.id,
|
|
148
|
+
author: c.author,
|
|
149
|
+
authorAssociation: c.authorAssociation,
|
|
150
|
+
reason: decision.reason,
|
|
151
|
+
viewerDidAuthor: c.viewerDidAuthor ?? null,
|
|
152
|
+
}, 'Skipped PR review comment from untrusted author');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (threadHasTrusted)
|
|
156
|
+
trustedThreads.push(thread);
|
|
157
|
+
}
|
|
158
|
+
unresolvedComments = trustedUnresolved;
|
|
159
|
+
trustedUnresolvedThreads = trustedThreads;
|
|
160
|
+
untrustedSkips = skips;
|
|
161
|
+
this.logger.info({
|
|
162
|
+
prNumber,
|
|
163
|
+
totalComments: allComments.length,
|
|
164
|
+
unresolvedThreads: unresolvedThreads.length,
|
|
165
|
+
trustedUnresolvedThreads: trustedThreads.length,
|
|
166
|
+
trustedUnresolvedComments: trustedUnresolved.length,
|
|
167
|
+
}, 'Fetched PR review threads (author-trust filtered)');
|
|
112
168
|
}
|
|
113
169
|
catch (error) {
|
|
114
|
-
this.logger.error({ error: String(error), prNumber, owner, repo }, 'Failed to fetch PR review
|
|
115
|
-
throw new Error(`Failed to fetch review
|
|
170
|
+
this.logger.error({ error: String(error), prNumber, owner, repo }, 'Failed to fetch PR review threads');
|
|
171
|
+
throw new Error(`Failed to fetch review threads for PR #${prNumber}: ${String(error)}`);
|
|
116
172
|
}
|
|
117
|
-
// 4.
|
|
118
|
-
|
|
173
|
+
// 4. Case A (#869): no unresolved threads at all — remove label and
|
|
174
|
+
// clear dedupe key.
|
|
175
|
+
if (unresolvedThreadCount === 0) {
|
|
119
176
|
this.logger.info({ prNumber, issueNumber }, 'No unresolved threads found — removing label and exiting');
|
|
120
177
|
await this.removeFeedbackLabel(github, owner, repo, issueNumber);
|
|
121
178
|
return;
|
|
122
179
|
}
|
|
180
|
+
// 4b. Case B (#869 / FR-002): unresolved threads exist, but every
|
|
181
|
+
// comment is untrusted (race-window residue that the monitor didn't
|
|
182
|
+
// catch, or degraded-identity mode). Retain the waiting-for label
|
|
183
|
+
// (FR-002) and clear the dedupe key so the next monitor poll can
|
|
184
|
+
// re-enqueue if the situation changes. Do NOT emit the "No unresolved
|
|
185
|
+
// threads found" log line (SC-002).
|
|
186
|
+
if (unresolvedComments.length === 0) {
|
|
187
|
+
this.logger.warn({
|
|
188
|
+
prNumber, issueNumber, owner, repo,
|
|
189
|
+
totalUnresolvedThreads: unresolvedThreadCount,
|
|
190
|
+
untrustedSkips: untrustedSkips.map((s) => ({
|
|
191
|
+
commentId: s.commentId,
|
|
192
|
+
author: s.author,
|
|
193
|
+
authorAssociation: s.authorAssociation,
|
|
194
|
+
reason: s.reason,
|
|
195
|
+
viewerDidAuthor: s.viewerDidAuthor ?? null,
|
|
196
|
+
})),
|
|
197
|
+
}, 'Zero-trusted unresolved threads — retaining waiting-for:address-pr-feedback label (FR-002)');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
123
200
|
// 5. Build structured prompt
|
|
124
201
|
const prompt = this.buildFeedbackPrompt(unresolvedComments, prNumber, issueNumber);
|
|
125
202
|
// 6. Spawn Claude CLI to address feedback
|
|
@@ -135,42 +212,92 @@ export class PrFeedbackHandler {
|
|
|
135
212
|
}
|
|
136
213
|
catch (error) {
|
|
137
214
|
this.logger.error({ error: String(error), prNumber, issueNumber, branchName }, 'Failed to commit and push changes — partial work may be lost');
|
|
138
|
-
// Don't throw here — we
|
|
139
|
-
|
|
215
|
+
// Don't throw here — we still need to run the blocked disposition.
|
|
216
|
+
}
|
|
217
|
+
// 7a. Disposition B short-circuit (#883): CLI did not complete cleanly OR
|
|
218
|
+
// there is no diff. Both mean the loop cannot advance on this cycle —
|
|
219
|
+
// add `blocked:stuck-feedback-loop` and leave `waiting-for:*` intact so
|
|
220
|
+
// the operator sees the pause. Do NOT reply, do NOT resolve, do NOT log
|
|
221
|
+
// success.
|
|
222
|
+
if (!success || !hasChanges) {
|
|
223
|
+
this.logger.warn({
|
|
224
|
+
prNumber,
|
|
225
|
+
issueNumber,
|
|
226
|
+
trigger: 'unresolvedThreads>0',
|
|
227
|
+
reason: !success ? 'cli-did-not-complete' : 'no-diff',
|
|
228
|
+
}, 'no-diff cycle — persisting trigger, entering blocked-stuck-feedback-loop disposition');
|
|
229
|
+
await this.addBlockedStuckFeedbackLoopLabel(github, owner, repo, issueNumber);
|
|
230
|
+
return;
|
|
140
231
|
}
|
|
141
|
-
|
|
142
|
-
|
|
232
|
+
// 7b. Happy path — CLI succeeded AND we have a real commit.
|
|
233
|
+
const shortSha = (await this.getHeadShortSha(checkoutPath)) ?? '<unknown>';
|
|
234
|
+
// 8. Interleaved reply→resolve per root thread (#883, Q4-C, FR-005,
|
|
235
|
+
// FR-007). Input-set closure: iterate `trustedUnresolvedThreads`
|
|
236
|
+
// captured at cycle start.
|
|
237
|
+
const outcomes = [];
|
|
238
|
+
for (const thread of trustedUnresolvedThreads) {
|
|
239
|
+
const replyBody = `Addressed in ${shortSha} — please review, and re-open this thread if it still falls short.`;
|
|
240
|
+
const replyResult = await this.tryPostReply(github, owner, repo, prNumber, thread.rootCommentId, replyBody);
|
|
241
|
+
const resolveResult = await this.tryResolveReviewThread(github, thread.id);
|
|
242
|
+
outcomes.push({
|
|
243
|
+
threadId: thread.id,
|
|
244
|
+
rootCommentId: thread.rootCommentId,
|
|
245
|
+
replyResult,
|
|
246
|
+
resolveResult,
|
|
247
|
+
});
|
|
143
248
|
}
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
// and remove the label. The code changes were pushed successfully.
|
|
153
|
-
this.logger.warn({ error: String(error), prNumber, issueNumber }, 'Failed to post some or all thread replies — partial success acceptable');
|
|
154
|
-
// Continue to label removal
|
|
155
|
-
}
|
|
249
|
+
// 9. Strict-decrease success test (#883, FR-006, FR-010).
|
|
250
|
+
const resolveSuccesses = outcomes.filter(o => o.resolveResult.ok).length;
|
|
251
|
+
const resolveFailures = outcomes.filter(o => !o.resolveResult.ok);
|
|
252
|
+
if (resolveSuccesses === 0) {
|
|
253
|
+
// FR-006 tail — commit landed but no thread transitioned.
|
|
254
|
+
this.logger.warn({ prNumber, issueNumber, outcomes }, 'commit pushed but resolve batch had zero successes — persisting trigger, entering blocked-stuck-feedback-loop disposition');
|
|
255
|
+
await this.addBlockedStuckFeedbackLoopLabel(github, owner, repo, issueNumber);
|
|
256
|
+
return;
|
|
156
257
|
}
|
|
157
|
-
|
|
158
|
-
|
|
258
|
+
// FR-010: one warn per persistently-failed thread, emitted BEFORE
|
|
259
|
+
// clearing the label so a silent partial failure is impossible.
|
|
260
|
+
for (const f of resolveFailures) {
|
|
261
|
+
this.logger.warn({
|
|
262
|
+
prNumber, issueNumber, owner, repo,
|
|
263
|
+
threadId: f.threadId,
|
|
264
|
+
rootCommentId: f.rootCommentId,
|
|
265
|
+
error: f.resolveResult.error,
|
|
266
|
+
remedy: 'Resolve the thread manually in the GitHub UI — the reply is already on the thread',
|
|
267
|
+
}, 'resolveReviewThread persistently failed after retries; label will still be cleared');
|
|
159
268
|
}
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
269
|
+
// 10. Label-clear LAST (Q4 tail). #926 FR-006: coalesce the happy-path
|
|
270
|
+
// clear into a single `removeLabels` call so `waiting-for:*` and
|
|
271
|
+
// `agent:in-progress` disappear in one request — no intermediate state
|
|
272
|
+
// where cockpit / auto observers see one label without the other.
|
|
273
|
+
// The `finally` clear becomes a no-op on this path (idempotent remove).
|
|
274
|
+
try {
|
|
275
|
+
await github.removeLabels(owner, repo, issueNumber, [
|
|
276
|
+
WAITING_FOR_ADDRESS_PR_FEEDBACK_LABEL,
|
|
277
|
+
AGENT_IN_PROGRESS_LABEL,
|
|
278
|
+
]);
|
|
279
|
+
this.logger.info({ issueNumber }, 'Removed waiting-for:address-pr-feedback + agent:in-progress labels (coalesced)');
|
|
165
280
|
}
|
|
166
|
-
|
|
167
|
-
this.logger.
|
|
281
|
+
catch (error) {
|
|
282
|
+
this.logger.warn({ error: String(error), issueNumber }, 'Failed to remove happy-path labels — non-fatal, finally will re-attempt in-progress clear');
|
|
168
283
|
}
|
|
284
|
+
this.logger.info({
|
|
285
|
+
prNumber, issueNumber,
|
|
286
|
+
resolveSuccesses,
|
|
287
|
+
resolveFailures: resolveFailures.length,
|
|
288
|
+
shortSha,
|
|
289
|
+
}, 'PR feedback cycle succeeded (strict decrease met)');
|
|
169
290
|
}
|
|
170
291
|
catch (error) {
|
|
171
292
|
this.logger.error({ error: String(error), prNumber, issueNumber, owner, repo }, 'Error processing PR feedback — task failed');
|
|
172
293
|
throw error;
|
|
173
294
|
}
|
|
295
|
+
finally {
|
|
296
|
+
// #926 SC-004: structural single-point clear. Every terminal exit
|
|
297
|
+
// (Case A, Case B, both blocked-stuck dispositions, happy path, and
|
|
298
|
+
// thrown errors) flows through here. Non-fatal on failure.
|
|
299
|
+
await this.clearInProgressLabel(github, owner, repo, issueNumber);
|
|
300
|
+
}
|
|
174
301
|
}
|
|
175
302
|
/**
|
|
176
303
|
* Build a structured prompt containing all unresolved review comments.
|
|
@@ -190,11 +317,13 @@ export class PrFeedbackHandler {
|
|
|
190
317
|
return `${idx + 1}. **${c.author}** (${location}):\n ${c.body}`;
|
|
191
318
|
})
|
|
192
319
|
.join('\n\n');
|
|
320
|
+
// #842: fence ingested thread content (author-trust filtered upstream).
|
|
321
|
+
const fenced = wrapUntrustedData(commentList, `PR #${prNumber} review comments`);
|
|
193
322
|
return `You are addressing PR review feedback for PR #${prNumber} (linked to issue #${issueNumber}).
|
|
194
323
|
|
|
195
324
|
The following unresolved review comments need to be addressed:
|
|
196
325
|
|
|
197
|
-
${
|
|
326
|
+
${fenced}
|
|
198
327
|
|
|
199
328
|
**Instructions:**
|
|
200
329
|
1. Read and understand each review comment above
|
|
@@ -343,50 +472,92 @@ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>`;
|
|
|
343
472
|
return true;
|
|
344
473
|
}
|
|
345
474
|
/**
|
|
346
|
-
* Post a reply
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
* FR-007: Partial failure is acceptable: if some replies fail, log warnings but continue.
|
|
350
|
-
* The handler will still remove the label even if some replies fail.
|
|
475
|
+
* Post a single reply targeting the root comment of a review thread.
|
|
476
|
+
* Returns a discriminated result — failures do not throw so the caller
|
|
477
|
+
* (#883 per-thread outcome loop) can aggregate results.
|
|
351
478
|
*/
|
|
352
|
-
async
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const replyBody = `I've addressed this feedback in the latest commit. Please review the changes.`;
|
|
360
|
-
await github.replyToPRComment(owner, repo, prNumber, comment.id, replyBody);
|
|
361
|
-
this.logger.debug({ prNumber, commentId: comment.id, path: comment.path, line: comment.line }, 'Posted reply to review thread');
|
|
362
|
-
successCount++;
|
|
363
|
-
}
|
|
364
|
-
catch (error) {
|
|
365
|
-
// FR-007: Continue processing other threads even if this one fails
|
|
366
|
-
failureCount++;
|
|
367
|
-
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');
|
|
368
|
-
}
|
|
479
|
+
async tryPostReply(github, owner, repo, prNumber, rootCommentId, body) {
|
|
480
|
+
try {
|
|
481
|
+
await github.replyToPRComment(owner, repo, prNumber, rootCommentId, body);
|
|
482
|
+
return { ok: true };
|
|
483
|
+
}
|
|
484
|
+
catch (error) {
|
|
485
|
+
return { ok: false, error: String(error) };
|
|
369
486
|
}
|
|
370
|
-
|
|
371
|
-
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Delegate to `github.resolveReviewThread` (which owns the 3× retry). Wraps
|
|
490
|
+
* the result in a discriminated union so the caller can aggregate outcomes
|
|
491
|
+
* without a try/catch (#883).
|
|
492
|
+
*/
|
|
493
|
+
async tryResolveReviewThread(github, threadId) {
|
|
494
|
+
try {
|
|
495
|
+
await github.resolveReviewThread(threadId);
|
|
496
|
+
return { ok: true };
|
|
497
|
+
}
|
|
498
|
+
catch (error) {
|
|
499
|
+
return { ok: false, error: String(error) };
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Read the short SHA of the just-pushed HEAD commit. Returns null when the
|
|
504
|
+
* git command fails; caller falls back to `<unknown>` in the reply body —
|
|
505
|
+
* the SHA is decoration, not termination logic (#883).
|
|
506
|
+
*/
|
|
507
|
+
async getHeadShortSha(checkoutPath) {
|
|
508
|
+
try {
|
|
509
|
+
const result = await executeCommand('git', ['rev-parse', '--short', 'HEAD'], { cwd: checkoutPath });
|
|
510
|
+
if (result.exitCode !== 0)
|
|
511
|
+
return null;
|
|
512
|
+
const sha = result.stdout.trim();
|
|
513
|
+
return sha.length > 0 ? sha : null;
|
|
372
514
|
}
|
|
373
|
-
|
|
374
|
-
|
|
515
|
+
catch {
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Add the `blocked:stuck-feedback-loop` label to signal that the fix cycle
|
|
521
|
+
* cannot advance and must not be re-enqueued until the operator removes the
|
|
522
|
+
* label. Non-fatal on failure — leaving `waiting-for:*` in place is the
|
|
523
|
+
* fallback safety net (#883).
|
|
524
|
+
*/
|
|
525
|
+
async addBlockedStuckFeedbackLoopLabel(github, owner, repo, issueNumber) {
|
|
526
|
+
try {
|
|
527
|
+
await github.addLabels(owner, repo, issueNumber, [BLOCKED_STUCK_FEEDBACK_LOOP_LABEL]);
|
|
528
|
+
this.logger.info({ issueNumber, label: BLOCKED_STUCK_FEEDBACK_LOOP_LABEL }, 'Added blocked:stuck-feedback-loop label');
|
|
529
|
+
}
|
|
530
|
+
catch (error) {
|
|
531
|
+
this.logger.warn({ error: String(error), issueNumber, label: BLOCKED_STUCK_FEEDBACK_LOOP_LABEL }, 'Failed to add blocked:stuck-feedback-loop label — non-fatal, waiting-for label persists');
|
|
375
532
|
}
|
|
376
|
-
// FR-007: Even if all replies fail, we don't throw — the caller will still remove the label
|
|
377
|
-
// because the code changes were pushed successfully
|
|
378
533
|
}
|
|
379
534
|
/**
|
|
380
535
|
* Remove the `waiting-for:address-pr-feedback` label from the linked issue.
|
|
381
536
|
*/
|
|
382
537
|
async removeFeedbackLabel(github, owner, repo, issueNumber) {
|
|
383
538
|
try {
|
|
384
|
-
await github.removeLabels(owner, repo, issueNumber, [
|
|
539
|
+
await github.removeLabels(owner, repo, issueNumber, [WAITING_FOR_ADDRESS_PR_FEEDBACK_LABEL]);
|
|
385
540
|
this.logger.info({ issueNumber }, 'Removed waiting-for:address-pr-feedback label');
|
|
386
541
|
}
|
|
387
542
|
catch (error) {
|
|
388
543
|
this.logger.warn({ error: String(error), issueNumber }, 'Failed to remove waiting-for:address-pr-feedback label — non-fatal');
|
|
389
544
|
}
|
|
390
545
|
}
|
|
546
|
+
/**
|
|
547
|
+
* Structural clear of `agent:in-progress` on the linked issue (#926 SC-004).
|
|
548
|
+
* Called from the shared `finally` block in `handle()` — runs on every
|
|
549
|
+
* terminal exit path (Cases A/B, both blocked-stuck dispositions, happy
|
|
550
|
+
* path, and thrown errors). Idempotent: GitHub's `removeLabels` is a no-op
|
|
551
|
+
* when the label is already absent, so the happy-path coalesced removal +
|
|
552
|
+
* this `finally` clear together produce at most one truthful post-state.
|
|
553
|
+
*/
|
|
554
|
+
async clearInProgressLabel(github, owner, repo, issueNumber) {
|
|
555
|
+
try {
|
|
556
|
+
await github.removeLabels(owner, repo, issueNumber, [AGENT_IN_PROGRESS_LABEL]);
|
|
557
|
+
}
|
|
558
|
+
catch (error) {
|
|
559
|
+
this.logger.warn({ error: String(error), issueNumber }, 'Failed to remove agent:in-progress label — non-fatal');
|
|
560
|
+
}
|
|
561
|
+
}
|
|
391
562
|
}
|
|
392
563
|
//# sourceMappingURL=pr-feedback-handler.js.map
|