@atolis-hq/wake 0.2.57 → 0.2.58
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/src/core/tick-runner.js +72 -23
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
- package/prompts/plan-review.md +9 -0
|
@@ -334,38 +334,62 @@ export function createTickRunner(deps) {
|
|
|
334
334
|
nextStage: null,
|
|
335
335
|
};
|
|
336
336
|
}
|
|
337
|
+
// GitHub rejects a review that would approve the PR's own author (422 "Can
|
|
338
|
+
// not approve your own pull request") — the default single-identity setup
|
|
339
|
+
// where implement and pr-review share one bot account. Permanent, not
|
|
340
|
+
// transient: never worth retrying, so it's treated as a policy block
|
|
341
|
+
// rather than left to propagate as an uncaught tick failure.
|
|
342
|
+
function isSelfApprovalError(error) {
|
|
343
|
+
return (error instanceof Error &&
|
|
344
|
+
error.status === 422 &&
|
|
345
|
+
error.message.toLowerCase().includes('approve your own pull request'));
|
|
346
|
+
}
|
|
337
347
|
async function performApprovedPrMergeActions(input) {
|
|
338
348
|
if (deps.prMergeActor === undefined ||
|
|
339
349
|
input.approvalResolution.targetResourceUri === undefined ||
|
|
340
350
|
input.approvalResolution.triggeringCommentId === undefined) {
|
|
341
|
-
return;
|
|
351
|
+
return { blocked: false };
|
|
342
352
|
}
|
|
343
353
|
const targetResourceUri = input.approvalResolution.targetResourceUri;
|
|
344
354
|
const commentId = input.approvalResolution.triggeringCommentId.replace(/[^a-z0-9]+/gi, '-');
|
|
345
355
|
const reviewBody = reviewerMessageFromApprovalComment(input.approvalResolution.triggeringCommentBody ?? '');
|
|
356
|
+
let blockedReason = null;
|
|
346
357
|
const approvedEventId = `pr-merge-approved-${commentId}`;
|
|
347
358
|
if (input.mergePolicy.approve &&
|
|
348
359
|
(await deps.stateStore.readEventEnvelope(approvedEventId)) === null) {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
occurredAt
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
360
|
+
let approved = true;
|
|
361
|
+
try {
|
|
362
|
+
await deps.prMergeActor.approve(targetResourceUri, reviewBody);
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
if (!isSelfApprovalError(error)) {
|
|
366
|
+
throw error;
|
|
367
|
+
}
|
|
368
|
+
approved = false;
|
|
369
|
+
blockedReason =
|
|
370
|
+
'Merge policy blocked the approval step because GitHub refuses a review that approves its own author (implement and pr-review run as the same bot identity). Either disable merge.approve and rely on merge.autoMerge alone, or configure a second reviewer identity.';
|
|
371
|
+
}
|
|
372
|
+
if (approved) {
|
|
373
|
+
const occurredAt = eventStampNow();
|
|
374
|
+
await deps.stateStore.appendEventEnvelope(createEventEnvelope({
|
|
375
|
+
eventId: approvedEventId,
|
|
376
|
+
workItemKey: input.projection.workItemKey,
|
|
377
|
+
streamScope: 'work-item',
|
|
378
|
+
direction: 'internal',
|
|
379
|
+
sourceSystem: 'wake',
|
|
380
|
+
sourceEventType: PR_REVIEW_APPROVED_EVENT,
|
|
381
|
+
sourceRefs: {
|
|
382
|
+
resourceUri: targetResourceUri,
|
|
383
|
+
commentId: input.approvalResolution.triggeringCommentId,
|
|
384
|
+
},
|
|
385
|
+
occurredAt,
|
|
386
|
+
ingestedAt: occurredAt,
|
|
387
|
+
trigger: 'context-only',
|
|
388
|
+
payload: {
|
|
389
|
+
idempotencyKey: `${input.approvalResolution.triggeringCommentId}:pr-review-approval`,
|
|
390
|
+
},
|
|
391
|
+
}));
|
|
392
|
+
}
|
|
369
393
|
}
|
|
370
394
|
const autoMergeEventId = `pr-auto-merge-enabled-${commentId}`;
|
|
371
395
|
if (input.mergePolicy.autoMerge &&
|
|
@@ -391,6 +415,10 @@ export function createTickRunner(deps) {
|
|
|
391
415
|
},
|
|
392
416
|
}));
|
|
393
417
|
}
|
|
418
|
+
if (blockedReason !== null) {
|
|
419
|
+
return { blocked: true, reason: blockedReason };
|
|
420
|
+
}
|
|
421
|
+
return { blocked: false };
|
|
394
422
|
}
|
|
395
423
|
// The one deterministic approval transition: /approved, wake:auto, and a
|
|
396
424
|
// watcher child's onSuccess.approve all resolve a pending approval through
|
|
@@ -1031,7 +1059,20 @@ export function createTickRunner(deps) {
|
|
|
1031
1059
|
action = entryStage.action ?? entryStageName;
|
|
1032
1060
|
claimedStage = entryStageName;
|
|
1033
1061
|
workspaceMode = entryStage.workspace;
|
|
1034
|
-
|
|
1062
|
+
// The triggering event's own body (e.g. a refine plan comment) is
|
|
1063
|
+
// durable and already local the moment this fires — projection.comments
|
|
1064
|
+
// only gets it once a later GitHub poll echoes it back, which a watcher
|
|
1065
|
+
// dispatched off the same-tick completion event can easily outrace.
|
|
1066
|
+
const triggeringEvent = watcherDispatch.trigger.kind === 'event'
|
|
1067
|
+
? await deps.stateStore.readEventEnvelope(watcherDispatch.trigger.eventId)
|
|
1068
|
+
: null;
|
|
1069
|
+
const triggeringBody = triggeringEvent?.payload.body;
|
|
1070
|
+
promptContextOverrides = {
|
|
1071
|
+
...entryStage.promptContext,
|
|
1072
|
+
...(typeof triggeringBody === 'string'
|
|
1073
|
+
? { parentPendingReviewBody: triggeringBody }
|
|
1074
|
+
: {}),
|
|
1075
|
+
};
|
|
1035
1076
|
workflowName = watcherDispatch.targetWorkflowName;
|
|
1036
1077
|
watcherStateKeyForRun = watcherKey({
|
|
1037
1078
|
workItemKey: watcherDispatch.projection.workItemKey,
|
|
@@ -1078,11 +1119,19 @@ export function createTickRunner(deps) {
|
|
|
1078
1119
|
workflowName,
|
|
1079
1120
|
});
|
|
1080
1121
|
}
|
|
1081
|
-
await performApprovedPrMergeActions({
|
|
1122
|
+
const mergeActionsResult = await performApprovedPrMergeActions({
|
|
1082
1123
|
projection: candidate,
|
|
1083
1124
|
approvalResolution,
|
|
1084
1125
|
mergePolicy,
|
|
1085
1126
|
});
|
|
1127
|
+
if (mergeActionsResult.blocked) {
|
|
1128
|
+
return await publishPrMergePolicyBlock({
|
|
1129
|
+
projection: candidate,
|
|
1130
|
+
approvalResolution,
|
|
1131
|
+
reason: mergeActionsResult.reason,
|
|
1132
|
+
workflowName,
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1086
1135
|
}
|
|
1087
1136
|
const approvalId = `approval-${candidate.issue.number}-${deps.clock.now().getTime()}`;
|
|
1088
1137
|
const approvedAt = deps.clock.now().toISOString();
|
package/dist/src/version.js
CHANGED
package/package.json
CHANGED
package/prompts/plan-review.md
CHANGED
|
@@ -9,6 +9,15 @@ You are Wake, in the PLAN-REVIEW workflow for work item {{workItemKey}}.
|
|
|
9
9
|
|
|
10
10
|
Your job is only to determine whether the pending plan on this work item is ready to proceed to the next stage.
|
|
11
11
|
|
|
12
|
+
The pending plan under review:
|
|
13
|
+
<wake-pending-plan>
|
|
14
|
+
{{#if parentPendingReviewBody}}
|
|
15
|
+
{{parentPendingReviewBody}}
|
|
16
|
+
{{else}}
|
|
17
|
+
(Wake did not attach the pending plan text to this run. Fall back to `gh issue view {{issueNumber}}` to read it directly — do not assume no plan exists just because it isn't shown above.)
|
|
18
|
+
{{/if}}
|
|
19
|
+
</wake-pending-plan>
|
|
20
|
+
|
|
12
21
|
Assess whether it is safe and correct to approve as-is, letting Wake proceed unattended. Weigh:
|
|
13
22
|
- Does the proposed plan actually address the issue as written, without silently narrowing, widening, or misreading the scope?
|
|
14
23
|
- Are there open questions in Wake's comment that were never actually answered (a refine pass sometimes states assumptions instead of asking — treat unstated but load-bearing assumptions the same as open questions)?
|