@h-rig/runtime 0.0.6-alpha.13 → 0.0.6-alpha.14
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/bin/rig-agent.js +1 -1
- package/dist/src/control-plane/authority-files.js +12 -6
- package/dist/src/control-plane/harness-main.js +430 -71
- package/dist/src/control-plane/hooks/completion-verification.js +469 -87
- package/dist/src/control-plane/native/git-ops.js +28 -7
- package/dist/src/control-plane/native/harness-cli.js +430 -71
- package/dist/src/control-plane/native/pr-automation.js +523 -86
- package/dist/src/control-plane/native/pr-review-gate.js +494 -69
- package/dist/src/control-plane/native/run-ops.js +12 -6
- package/dist/src/control-plane/native/task-ops.js +466 -105
- package/dist/src/control-plane/native/verifier.js +466 -107
- package/dist/src/control-plane/native/workspace-ops.js +12 -6
- package/native/darwin-arm64/rig-git +0 -0
- package/native/darwin-arm64/rig-git.build-manifest.json +1 -1
- package/native/darwin-arm64/rig-shell +0 -0
- package/native/darwin-arm64/rig-shell.build-manifest.json +1 -1
- package/native/darwin-arm64/rig-tools +0 -0
- package/native/darwin-arm64/rig-tools.build-manifest.json +1 -1
- package/package.json +6 -6
|
@@ -3714,7 +3714,7 @@ function stripHtml(input) {
|
|
|
3714
3714
|
}
|
|
3715
3715
|
function containsBlockerText(input) {
|
|
3716
3716
|
const text = stripHtml(input).replace(/\b(?:no|without|zero)\s+blockers?\b/gi, " ").replace(/\bno\s+changes\s+requested\b/gi, " ");
|
|
3717
|
-
return /not safe(?: to merge)?|unsafe(?: to merge)?|do not merge|cannot merge|blockers?|must fix|changes requested|please fix|needs? fix|fix this|address this/i.test(text);
|
|
3717
|
+
return /not safe(?: to merge)?|unsafe(?: to merge)?|do not merge|cannot merge|blockers?|must fix|changes requested|please fix|needs? fix|fix this|address this|\breject(?:ed|ion)?\b|\bskip(?:ped)?\b|status\s*:\s*(?:reject(?:ed)?|skip(?:ped)?|failed)/i.test(text);
|
|
3718
3718
|
}
|
|
3719
3719
|
function isStrictFiveOfFive(score) {
|
|
3720
3720
|
return score.value === 5 && score.scale === 5;
|
|
@@ -3722,6 +3722,189 @@ function isStrictFiveOfFive(score) {
|
|
|
3722
3722
|
function containsConflictingScoreText(input) {
|
|
3723
3723
|
return parseGreptileScores(input).some((score) => !isStrictFiveOfFive(score));
|
|
3724
3724
|
}
|
|
3725
|
+
function greptileStatusVerdict(status) {
|
|
3726
|
+
const normalized = String(status ?? "").trim().toUpperCase().replace(/[\s-]+/g, "_");
|
|
3727
|
+
if (!normalized)
|
|
3728
|
+
return null;
|
|
3729
|
+
if (["APPROVE", "APPROVED"].includes(normalized))
|
|
3730
|
+
return "approved";
|
|
3731
|
+
if (["REJECT", "REJECTED", "CHANGES_REQUESTED", "CHANGE_REQUESTED"].includes(normalized))
|
|
3732
|
+
return "rejected";
|
|
3733
|
+
if (["SKIP", "SKIPPED"].includes(normalized))
|
|
3734
|
+
return "skipped";
|
|
3735
|
+
if (["FAIL", "FAILED", "FAILURE", "ERROR"].includes(normalized))
|
|
3736
|
+
return "failed";
|
|
3737
|
+
if (["PENDING", "QUEUED", "IN_PROGRESS", "RUNNING", "STARTED", "REQUESTED", "REVIEWING_FILES", "GENERATING_SUMMARY"].includes(normalized))
|
|
3738
|
+
return "pending";
|
|
3739
|
+
if (["COMPLETE", "COMPLETED"].includes(normalized))
|
|
3740
|
+
return "completed";
|
|
3741
|
+
return null;
|
|
3742
|
+
}
|
|
3743
|
+
function isBlockingGreptileVerdict(verdict) {
|
|
3744
|
+
return verdict === "rejected" || verdict === "skipped" || verdict === "failed";
|
|
3745
|
+
}
|
|
3746
|
+
function greptileRequestTimeoutMs(env) {
|
|
3747
|
+
const fallback = 30000;
|
|
3748
|
+
const parsed = Number.parseInt(env.GREPTILE_REQUEST_TIMEOUT_MS || `${fallback}`, 10);
|
|
3749
|
+
return Number.isFinite(parsed) && parsed >= 1000 ? parsed : fallback;
|
|
3750
|
+
}
|
|
3751
|
+
function normalizeGreptileMcpCodeReview(entry, fallbackId) {
|
|
3752
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
3753
|
+
return null;
|
|
3754
|
+
const record = entry;
|
|
3755
|
+
const id = typeof record.id === "string" ? record.id.trim() : fallbackId?.trim() ?? "";
|
|
3756
|
+
if (!id)
|
|
3757
|
+
return null;
|
|
3758
|
+
const metadataRecord = record.metadata && typeof record.metadata === "object" && !Array.isArray(record.metadata) ? record.metadata : null;
|
|
3759
|
+
return {
|
|
3760
|
+
id,
|
|
3761
|
+
status: typeof record.status === "string" ? record.status : null,
|
|
3762
|
+
createdAt: typeof record.createdAt === "string" ? record.createdAt : null,
|
|
3763
|
+
body: typeof record.body === "string" ? record.body : null,
|
|
3764
|
+
metadata: metadataRecord ? { checkHeadSha: typeof metadataRecord.checkHeadSha === "string" ? metadataRecord.checkHeadSha : null } : null
|
|
3765
|
+
};
|
|
3766
|
+
}
|
|
3767
|
+
function uniqueGreptileCodeReviews(reviews) {
|
|
3768
|
+
const seen = new Set;
|
|
3769
|
+
const unique2 = [];
|
|
3770
|
+
for (const review of reviews) {
|
|
3771
|
+
if (seen.has(review.id))
|
|
3772
|
+
continue;
|
|
3773
|
+
seen.add(review.id);
|
|
3774
|
+
unique2.push(review);
|
|
3775
|
+
}
|
|
3776
|
+
return unique2;
|
|
3777
|
+
}
|
|
3778
|
+
function selectGreptileApiReviewsForGate(reviews, headSha) {
|
|
3779
|
+
const sorted = [...reviews].sort((left, right) => Date.parse(right.createdAt ?? "") - Date.parse(left.createdAt ?? ""));
|
|
3780
|
+
const current = headSha ? sorted.filter((review) => review.metadata?.checkHeadSha === headSha) : [];
|
|
3781
|
+
const untied = sorted.filter((review) => !review.metadata?.checkHeadSha);
|
|
3782
|
+
const latest = sorted.slice(0, 1);
|
|
3783
|
+
return uniqueGreptileCodeReviews([...current, ...untied, ...latest]);
|
|
3784
|
+
}
|
|
3785
|
+
function greptileApiSignalFromCodeReview(review, details) {
|
|
3786
|
+
const selected = details ?? review;
|
|
3787
|
+
return {
|
|
3788
|
+
id: selected.id || review.id,
|
|
3789
|
+
body: selected.body ?? review.body ?? null,
|
|
3790
|
+
reviewedSha: selected.metadata?.checkHeadSha ?? review.metadata?.checkHeadSha ?? null,
|
|
3791
|
+
status: selected.status ?? review.status ?? null
|
|
3792
|
+
};
|
|
3793
|
+
}
|
|
3794
|
+
async function callGreptileMcpToolForGate(input) {
|
|
3795
|
+
const controller = new AbortController;
|
|
3796
|
+
const timeoutId = setTimeout(() => {
|
|
3797
|
+
controller.abort(new Error(`Greptile MCP tool ${input.name} timed out after ${input.timeoutMs}ms.`));
|
|
3798
|
+
}, input.timeoutMs);
|
|
3799
|
+
let response;
|
|
3800
|
+
try {
|
|
3801
|
+
response = await input.fetchFn(input.apiBase, {
|
|
3802
|
+
method: "POST",
|
|
3803
|
+
headers: {
|
|
3804
|
+
Authorization: `Bearer ${input.apiKey}`,
|
|
3805
|
+
"Content-Type": "application/json"
|
|
3806
|
+
},
|
|
3807
|
+
body: JSON.stringify({
|
|
3808
|
+
jsonrpc: "2.0",
|
|
3809
|
+
id: `rig-strict-gate-${input.name}-${Date.now()}`,
|
|
3810
|
+
method: "tools/call",
|
|
3811
|
+
params: { name: input.name, arguments: input.args }
|
|
3812
|
+
}),
|
|
3813
|
+
signal: controller.signal
|
|
3814
|
+
});
|
|
3815
|
+
} catch (error) {
|
|
3816
|
+
if (controller.signal.aborted) {
|
|
3817
|
+
throw controller.signal.reason instanceof Error ? controller.signal.reason : new Error(`Greptile MCP tool ${input.name} timed out after ${input.timeoutMs}ms.`);
|
|
3818
|
+
}
|
|
3819
|
+
throw error;
|
|
3820
|
+
} finally {
|
|
3821
|
+
clearTimeout(timeoutId);
|
|
3822
|
+
}
|
|
3823
|
+
const raw = await response.text();
|
|
3824
|
+
if (!response.ok) {
|
|
3825
|
+
throw new Error(`HTTP ${response.status}: ${raw}`);
|
|
3826
|
+
}
|
|
3827
|
+
let envelope;
|
|
3828
|
+
try {
|
|
3829
|
+
envelope = JSON.parse(raw);
|
|
3830
|
+
} catch {
|
|
3831
|
+
throw new Error(`Malformed MCP response: ${raw}`);
|
|
3832
|
+
}
|
|
3833
|
+
if (envelope.error?.message) {
|
|
3834
|
+
throw new Error(envelope.error.message);
|
|
3835
|
+
}
|
|
3836
|
+
const text = (envelope.result?.content ?? []).filter((item) => item.type === "text" && typeof item.text === "string").map((item) => item.text ?? "").join(`
|
|
3837
|
+
`).trim();
|
|
3838
|
+
if (!text) {
|
|
3839
|
+
throw new Error(`MCP tool ${input.name} returned no text payload.`);
|
|
3840
|
+
}
|
|
3841
|
+
return text;
|
|
3842
|
+
}
|
|
3843
|
+
async function callGreptileMcpToolJsonForGate(input) {
|
|
3844
|
+
const text = await callGreptileMcpToolForGate(input);
|
|
3845
|
+
try {
|
|
3846
|
+
return JSON.parse(text);
|
|
3847
|
+
} catch {
|
|
3848
|
+
throw new Error(`MCP tool ${input.name} returned malformed JSON: ${text}`);
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
async function collectConfiguredGreptileApiSignals(input) {
|
|
3852
|
+
if (!input.enabled || input.options?.enabled === false) {
|
|
3853
|
+
return { signals: [], errors: [] };
|
|
3854
|
+
}
|
|
3855
|
+
const env = input.options?.env ?? process.env;
|
|
3856
|
+
const secrets = resolveRuntimeSecrets(env);
|
|
3857
|
+
const apiKey = secrets.GREPTILE_API_KEY?.trim() ?? "";
|
|
3858
|
+
if (!apiKey) {
|
|
3859
|
+
return { signals: [], errors: [] };
|
|
3860
|
+
}
|
|
3861
|
+
const fetchFn = input.options?.fetch ?? globalThis.fetch;
|
|
3862
|
+
if (typeof fetchFn !== "function") {
|
|
3863
|
+
return { signals: [], errors: ["Greptile API/MCP evidence read failed: fetch is not available."] };
|
|
3864
|
+
}
|
|
3865
|
+
const apiBase = secrets.GREPTILE_API_BASE?.trim() || "https://api.greptile.com/mcp";
|
|
3866
|
+
const remote = secrets.GREPTILE_REMOTE?.trim() || "github";
|
|
3867
|
+
const repository = secrets.GREPTILE_REPOSITORY?.trim() || input.repoName;
|
|
3868
|
+
const defaultBranch = secrets.GREPTILE_DEFAULT_BRANCH?.trim() || input.baseRefName?.trim() || "main";
|
|
3869
|
+
const timeoutMs = greptileRequestTimeoutMs(env);
|
|
3870
|
+
try {
|
|
3871
|
+
const listPayload = await callGreptileMcpToolJsonForGate({
|
|
3872
|
+
apiBase,
|
|
3873
|
+
apiKey,
|
|
3874
|
+
name: "list_code_reviews",
|
|
3875
|
+
args: {
|
|
3876
|
+
name: repository,
|
|
3877
|
+
remote,
|
|
3878
|
+
defaultBranch,
|
|
3879
|
+
prNumber: input.prNumber,
|
|
3880
|
+
limit: 20
|
|
3881
|
+
},
|
|
3882
|
+
timeoutMs,
|
|
3883
|
+
fetchFn
|
|
3884
|
+
});
|
|
3885
|
+
const reviews = (listPayload.codeReviews ?? []).map((entry) => normalizeGreptileMcpCodeReview(entry)).filter((review) => !!review);
|
|
3886
|
+
const selectedReviews = selectGreptileApiReviewsForGate(reviews, input.headSha);
|
|
3887
|
+
const signals = [];
|
|
3888
|
+
for (const review of selectedReviews) {
|
|
3889
|
+
const detailsPayload = await callGreptileMcpToolJsonForGate({
|
|
3890
|
+
apiBase,
|
|
3891
|
+
apiKey,
|
|
3892
|
+
name: "get_code_review",
|
|
3893
|
+
args: { codeReviewId: review.id },
|
|
3894
|
+
timeoutMs,
|
|
3895
|
+
fetchFn
|
|
3896
|
+
});
|
|
3897
|
+
const details = normalizeGreptileMcpCodeReview(detailsPayload.codeReview, review.id) ?? review;
|
|
3898
|
+
signals.push(greptileApiSignalFromCodeReview(review, details));
|
|
3899
|
+
}
|
|
3900
|
+
return { signals, errors: [] };
|
|
3901
|
+
} catch (error) {
|
|
3902
|
+
return {
|
|
3903
|
+
signals: [],
|
|
3904
|
+
errors: [`Greptile API/MCP evidence read failed: ${error instanceof Error ? error.message : String(error)}`]
|
|
3905
|
+
};
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3725
3908
|
function firstString(record, keys) {
|
|
3726
3909
|
for (const key of keys) {
|
|
3727
3910
|
const value = record[key];
|
|
@@ -3848,7 +4031,7 @@ function normalizeReviewThread(entry) {
|
|
|
3848
4031
|
function relevantIssueComment(comment) {
|
|
3849
4032
|
const login = comment.user?.login ?? comment.author?.login ?? "";
|
|
3850
4033
|
const body = comment.body ?? "";
|
|
3851
|
-
return isGreptileGithubLogin(login) || /greptile|
|
|
4034
|
+
return isGreptileGithubLogin(login) || containsBlockerText(body) || /greptile|score|confidence|\b\d+\s*\/\s*5\b/i.test(body);
|
|
3852
4035
|
}
|
|
3853
4036
|
function latestThreadComment(thread) {
|
|
3854
4037
|
const nodes = thread.comments?.nodes ?? [];
|
|
@@ -3884,7 +4067,8 @@ function makeGreptileSignal(input) {
|
|
|
3884
4067
|
const scores = parseGreptileScores(input.body);
|
|
3885
4068
|
const reviewedSha = input.reviewedSha?.trim() || null;
|
|
3886
4069
|
const current = reviewedSha ? reviewedSha === input.currentHeadSha : null;
|
|
3887
|
-
const
|
|
4070
|
+
const verdict = input.verdict ?? null;
|
|
4071
|
+
const blocker = input.blocker ?? (isBlockingGreptileVerdict(verdict) || containsBlockerText(input.body));
|
|
3888
4072
|
const explicitApproval = input.explicitApproval ?? false;
|
|
3889
4073
|
return {
|
|
3890
4074
|
source: input.source,
|
|
@@ -3896,6 +4080,7 @@ function makeGreptileSignal(input) {
|
|
|
3896
4080
|
score: scores[0] ?? null,
|
|
3897
4081
|
scores,
|
|
3898
4082
|
explicitApproval,
|
|
4083
|
+
verdict,
|
|
3899
4084
|
blocker,
|
|
3900
4085
|
actionable: input.actionable ?? blocker,
|
|
3901
4086
|
bodyExcerpt: bodyExcerpt(input.body),
|
|
@@ -3918,9 +4103,9 @@ function collectGreptileSignals(evidence) {
|
|
|
3918
4103
|
for (const context of contextSources) {
|
|
3919
4104
|
if (!context.body.trim())
|
|
3920
4105
|
continue;
|
|
3921
|
-
if (!/greptile|score|confidence|\b\d+\s*\/\s*5\b|blocker|unsafe|not safe|do not merge|changes requested/i.test(context.body))
|
|
3922
|
-
continue;
|
|
3923
4106
|
const contextBlocker = containsBlockerText(context.body);
|
|
4107
|
+
if (!contextBlocker && !/greptile|score|confidence|\b\d+\s*\/\s*5\b/i.test(context.body))
|
|
4108
|
+
continue;
|
|
3924
4109
|
signals.push(makeGreptileSignal({
|
|
3925
4110
|
source: context.source,
|
|
3926
4111
|
body: context.body,
|
|
@@ -3933,16 +4118,16 @@ function collectGreptileSignals(evidence) {
|
|
|
3933
4118
|
for (const apiSignal of evidence.apiSignals ?? []) {
|
|
3934
4119
|
const body = [apiSignal.status ? `Status: ${apiSignal.status}` : "", apiSignal.body ?? ""].filter(Boolean).join(`
|
|
3935
4120
|
|
|
3936
|
-
`);
|
|
3937
|
-
|
|
3938
|
-
continue;
|
|
4121
|
+
`) || "Status: UNKNOWN";
|
|
4122
|
+
const verdict = greptileStatusVerdict(apiSignal.status);
|
|
3939
4123
|
signals.push(makeGreptileSignal({
|
|
3940
4124
|
source: "api",
|
|
3941
4125
|
body,
|
|
3942
4126
|
currentHeadSha: evidence.currentHeadSha,
|
|
3943
4127
|
trusted: true,
|
|
3944
4128
|
reviewedSha: apiSignal.reviewedSha ?? null,
|
|
3945
|
-
explicitApproval:
|
|
4129
|
+
explicitApproval: verdict === "approved",
|
|
4130
|
+
verdict
|
|
3946
4131
|
}));
|
|
3947
4132
|
}
|
|
3948
4133
|
for (const review of evidence.reviews) {
|
|
@@ -3967,20 +4152,6 @@ function collectGreptileSignals(evidence) {
|
|
|
3967
4152
|
blocker: state === "CHANGES_REQUESTED" || undefined
|
|
3968
4153
|
}));
|
|
3969
4154
|
}
|
|
3970
|
-
for (const comment of evidence.changedFileReviewComments) {
|
|
3971
|
-
const login = commentAuthorLogin(comment);
|
|
3972
|
-
const body = comment.body ?? "";
|
|
3973
|
-
if (!body.trim() || !isGreptileGithubLogin(login))
|
|
3974
|
-
continue;
|
|
3975
|
-
signals.push(makeGreptileSignal({
|
|
3976
|
-
source: "changed-file-comment",
|
|
3977
|
-
body,
|
|
3978
|
-
currentHeadSha: evidence.currentHeadSha,
|
|
3979
|
-
trusted: true,
|
|
3980
|
-
authorLogin: login,
|
|
3981
|
-
reviewedSha: comment.commit_id ?? comment.original_commit_id ?? null
|
|
3982
|
-
}));
|
|
3983
|
-
}
|
|
3984
4155
|
for (const comment of evidence.relevantIssueComments) {
|
|
3985
4156
|
const login = commentAuthorLogin(comment);
|
|
3986
4157
|
const body = comment.body ?? "";
|
|
@@ -4088,10 +4259,17 @@ function deriveGreptileEvidence(input) {
|
|
|
4088
4259
|
const isCurrentOrUntied = (signal) => !signal.reviewedSha || signal.reviewedSha === input.currentHeadSha;
|
|
4089
4260
|
const currentOrUntiedScoreEntries = allScoreEntries.filter((entry) => isCurrentOrUntied(entry.signal));
|
|
4090
4261
|
const lowScoreEntries = currentOrUntiedScoreEntries.filter((entry) => !isStrictFiveOfFive(entry.score));
|
|
4091
|
-
const
|
|
4262
|
+
const currentPendingApiSignals = trustedSignals.filter((signal) => signal.source === "api" && signal.verdict === "pending" && isCurrentOrUntied(signal));
|
|
4263
|
+
const signalCanApproveByScore = (signal) => {
|
|
4264
|
+
if (signal.source === "api")
|
|
4265
|
+
return signal.verdict === "approved" || signal.verdict === "completed";
|
|
4266
|
+
return signal.verdict !== "pending" && !isBlockingGreptileVerdict(signal.verdict);
|
|
4267
|
+
};
|
|
4268
|
+
const approvingScoreEntry = trustedScoreEntries.find((entry) => entry.signal.reviewedSha === input.currentHeadSha && entry.signal.source !== "github-check" && signalCanApproveByScore(entry.signal) && isStrictFiveOfFive(entry.score)) ?? null;
|
|
4269
|
+
const approvingExplicitSignal = trustedSignals.find((signal) => signal.source === "api" && signal.reviewedSha === input.currentHeadSha && signal.explicitApproval === true && !signal.blocker) ?? null;
|
|
4092
4270
|
const approvedByScore = !!approvingScoreEntry;
|
|
4093
|
-
const approvedByExplicitMapping =
|
|
4094
|
-
const approvingSignal = approvingScoreEntry?.signal ??
|
|
4271
|
+
const approvedByExplicitMapping = !!approvingExplicitSignal;
|
|
4272
|
+
const approvingSignal = approvingScoreEntry?.signal ?? approvingExplicitSignal;
|
|
4095
4273
|
const lowestScore = lowScoreEntries.map((entry) => entry.score).sort((left, right) => left.value - right.value)[0] ?? null;
|
|
4096
4274
|
const score = lowestScore ?? approvingScoreEntry?.score ?? trustedScoreEntries[0]?.score ?? contextScoreEntries[0]?.score ?? null;
|
|
4097
4275
|
const failedGreptileChecks = input.checks.filter((check) => isGreptileLabel(checkName(check)) && isFailingCheck(check)).map((check) => `${checkName(check)} (${checkState(check) || "failed"})`);
|
|
@@ -4120,13 +4298,14 @@ function deriveGreptileEvidence(input) {
|
|
|
4120
4298
|
const completedState = ["APPROVED", "COMMENTED", "CHANGES_REQUESTED"].includes(state) || !!review.body?.trim();
|
|
4121
4299
|
return completedState && review.commit_id === input.currentHeadSha;
|
|
4122
4300
|
});
|
|
4301
|
+
const completedGreptileApi = trustedSignals.some((signal) => signal.source === "api" && signal.reviewedSha === input.currentHeadSha && (signal.verdict === "approved" || signal.verdict === "rejected" || signal.verdict === "skipped" || signal.verdict === "failed" || signal.verdict === "completed"));
|
|
4123
4302
|
const approvalReviewedSha = approvingSignal?.reviewedSha ?? null;
|
|
4124
4303
|
const reviewedSha = approvalReviewedSha ?? staleSignals[0]?.reviewedSha ?? trustedSignals.map((signal) => signal.reviewedSha ?? null).find(Boolean) ?? null;
|
|
4125
4304
|
const fresh = !!approvalReviewedSha && approvalReviewedSha === input.currentHeadSha;
|
|
4126
|
-
const completed = completedGreptileCheck || completedGreptileReview ||
|
|
4305
|
+
const completed = completedGreptileCheck || completedGreptileReview || completedGreptileApi || !!approvingSignal;
|
|
4127
4306
|
const hasGreptileEvidence = trustedSignals.length > 0 || signals.some((signal) => /greptile/i.test(signal.body));
|
|
4128
|
-
const approved = fresh && completed && !blockers.length && !unresolvedComments.length && (approvedByScore || approvedByExplicitMapping);
|
|
4129
|
-
const mapping = !hasGreptileEvidence ? "missing" : staleSignals.length > 0 && !approvingSignal ? "stale" : approvedByScore ? "score-5-of-5" : "unproven";
|
|
4307
|
+
const approved = fresh && completed && !blockers.length && !unresolvedComments.length && currentPendingApiSignals.length === 0 && (approvedByScore || approvedByExplicitMapping);
|
|
4308
|
+
const mapping = !hasGreptileEvidence ? "missing" : staleSignals.length > 0 && !approvingSignal ? "stale" : approvedByScore ? "score-5-of-5" : approvedByExplicitMapping ? "explicit-approved" : "unproven";
|
|
4130
4309
|
const source = approvingSignal?.source === "api" ? "api" : approvingSignal?.source === "github-review" ? "github-review" : approvingSignal?.source === "changed-file-comment" || approvingSignal?.source === "issue-comment" || approvingSignal?.source === "review-thread" ? "github-comment" : greptileReviews.length > 0 && greptileChecks.length > 0 ? "combined" : greptileReviews.length > 0 ? "github-review" : greptileChecks.length > 0 ? "github-check" : signals.some((signal) => signal.source === "pr-body" || signal.source === "pr-title") ? "pr-body" : "missing";
|
|
4131
4310
|
return {
|
|
4132
4311
|
source,
|
|
@@ -4233,6 +4412,7 @@ async function collectPrReviewEvidence(input) {
|
|
|
4233
4412
|
readErrors.push("gh pr view did not return required reviews array");
|
|
4234
4413
|
}
|
|
4235
4414
|
const headSha = firstString(view, ["headRefOid", "headSha", "head_sha"]);
|
|
4415
|
+
const baseRefName = firstString(view, ["baseRefName"]);
|
|
4236
4416
|
const statusCheckRollup = arrayField(view, "statusCheckRollup").map(normalizeStatusCheck).filter((entry) => !!entry);
|
|
4237
4417
|
const reviews = arrayField(view, "reviews").map(normalizeReview).filter((entry) => !!entry);
|
|
4238
4418
|
const reviewCommentsRead = await runJsonArray(input.command, ["api", `repos/${parsed.repoName}/pulls/${parsed.prNumber}/comments`, "--paginate", "--slurp"], input.projectRoot);
|
|
@@ -4270,8 +4450,19 @@ async function collectPrReviewEvidence(input) {
|
|
|
4270
4450
|
}
|
|
4271
4451
|
}
|
|
4272
4452
|
const checksWithGreptileDetails = [...statusCheckRollup, ...greptileCheckDetails];
|
|
4453
|
+
const shouldCollectConfiguredGreptileApi = input.greptileApi?.enabled !== false;
|
|
4454
|
+
const configuredGreptileApiRead = await collectConfiguredGreptileApiSignals({
|
|
4455
|
+
enabled: shouldCollectConfiguredGreptileApi,
|
|
4456
|
+
options: input.greptileApi,
|
|
4457
|
+
repoName: parsed.repoName,
|
|
4458
|
+
prNumber: parsed.prNumber,
|
|
4459
|
+
headSha,
|
|
4460
|
+
baseRefName
|
|
4461
|
+
});
|
|
4462
|
+
readErrors.push(...configuredGreptileApiRead.errors);
|
|
4463
|
+
const apiSignals = [...input.apiSignals ?? [], ...configuredGreptileApiRead.signals];
|
|
4273
4464
|
const checkFailures = statusCheckRollup.filter((check) => !isGreptileLabel(checkName(check)) && isFailingCheck(check) && !isAllowedFailure(checkName(check), input.allowedFailures ?? [])).map((check) => `Check failed: ${checkName(check)}${check.detailsUrl || check.link ? ` (${check.detailsUrl ?? check.link})` : ""}`);
|
|
4274
|
-
const pendingChecks = statusCheckRollup.filter((check) => isPendingCheck(check) && !isAllowedFailure(checkName(check), input.allowedFailures ?? [])).map((check) => `Check pending: ${checkName(check)}`);
|
|
4465
|
+
const pendingChecks = statusCheckRollup.filter((check) => isPendingCheck(check) && (isGreptileLabel(checkName(check)) || !isAllowedFailure(checkName(check), input.allowedFailures ?? []))).map((check) => `Check pending: ${checkName(check)}`);
|
|
4275
4466
|
const evidenceBase = {
|
|
4276
4467
|
title: firstString(view, ["title"]),
|
|
4277
4468
|
body: firstString(view, ["body"]),
|
|
@@ -4281,7 +4472,7 @@ async function collectPrReviewEvidence(input) {
|
|
|
4281
4472
|
reviewThreads,
|
|
4282
4473
|
checks: checksWithGreptileDetails,
|
|
4283
4474
|
currentHeadSha: headSha,
|
|
4284
|
-
apiSignals
|
|
4475
|
+
apiSignals
|
|
4285
4476
|
};
|
|
4286
4477
|
const greptile = deriveGreptileEvidence(evidenceBase);
|
|
4287
4478
|
return {
|
|
@@ -4292,7 +4483,7 @@ async function collectPrReviewEvidence(input) {
|
|
|
4292
4483
|
body: evidenceBase.body,
|
|
4293
4484
|
headSha,
|
|
4294
4485
|
headRefName: firstString(view, ["headRefName"]),
|
|
4295
|
-
baseRefName
|
|
4486
|
+
baseRefName,
|
|
4296
4487
|
state: firstString(view, ["state"]),
|
|
4297
4488
|
isDraft: typeof view.isDraft === "boolean" ? view.isDraft : null,
|
|
4298
4489
|
mergeable: firstString(view, ["mergeable"]),
|
|
@@ -4309,66 +4500,224 @@ async function collectPrReviewEvidence(input) {
|
|
|
4309
4500
|
greptile
|
|
4310
4501
|
};
|
|
4311
4502
|
}
|
|
4503
|
+
function capGateMessage(value, maxChars = 1200) {
|
|
4504
|
+
const normalized = value.trim();
|
|
4505
|
+
return normalized.length > maxChars ? `${normalized.slice(0, maxChars)}
|
|
4506
|
+
[truncated for gate summary; see full evidence artifact]` : normalized;
|
|
4507
|
+
}
|
|
4312
4508
|
function evaluateEvidence(evidence) {
|
|
4313
|
-
const
|
|
4509
|
+
const reasonDetails = [];
|
|
4314
4510
|
const warnings = [];
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4511
|
+
const seen = new Set;
|
|
4512
|
+
const addReason = (reason) => {
|
|
4513
|
+
const capped = { ...reason, message: capGateMessage(reason.message) };
|
|
4514
|
+
const key = `${capped.code}:${capped.message}`;
|
|
4515
|
+
if (seen.has(key))
|
|
4516
|
+
return;
|
|
4517
|
+
seen.add(key);
|
|
4518
|
+
reasonDetails.push(capped);
|
|
4519
|
+
};
|
|
4520
|
+
const greptile = evidence.greptile;
|
|
4521
|
+
const staleSignal = greptile.signals.find((signal) => signal.reviewedSha && signal.reviewedSha !== evidence.headSha);
|
|
4522
|
+
const hasPendingGreptileCheck = evidence.pendingChecks.some((check) => /greptile/i.test(check));
|
|
4523
|
+
const pendingGreptileApiSignals = greptile.signals.filter((signal) => signal.source === "api" && signal.verdict === "pending" && (!signal.reviewedSha || signal.reviewedSha === evidence.headSha));
|
|
4524
|
+
const unknownGreptileApiSignals = greptile.signals.filter((signal) => signal.source === "api" && !signal.verdict && (!signal.reviewedSha || signal.reviewedSha === evidence.headSha));
|
|
4525
|
+
const awaitingFreshGreptileProof = hasPendingGreptileCheck || pendingGreptileApiSignals.length > 0 || !greptile.completed || greptile.mapping === "missing" || greptile.mapping === "stale";
|
|
4526
|
+
for (const error of evidence.readErrors) {
|
|
4527
|
+
addReason({
|
|
4528
|
+
code: "read_error",
|
|
4529
|
+
reasonClass: "reject",
|
|
4530
|
+
surface: error.startsWith("Greptile API/MCP") ? "greptile" : "github",
|
|
4531
|
+
suggestedAction: "needs_attention",
|
|
4532
|
+
message: `Required PR evidence surface could not be read completely: ${error}`,
|
|
4533
|
+
headSha: evidence.headSha || null
|
|
4534
|
+
});
|
|
4535
|
+
}
|
|
4536
|
+
if (!evidence.headSha) {
|
|
4537
|
+
addReason({
|
|
4538
|
+
code: "missing_head_sha",
|
|
4539
|
+
reasonClass: "reject",
|
|
4540
|
+
surface: "github",
|
|
4541
|
+
suggestedAction: "needs_attention",
|
|
4542
|
+
message: "PR head SHA could not be read; current-head Greptile approval cannot be proven.",
|
|
4543
|
+
headSha: null
|
|
4544
|
+
});
|
|
4545
|
+
}
|
|
4546
|
+
for (const failure of evidence.checkFailures) {
|
|
4547
|
+
addReason({
|
|
4548
|
+
code: "ci_failed",
|
|
4549
|
+
reasonClass: "reject",
|
|
4550
|
+
surface: "ci",
|
|
4551
|
+
suggestedAction: "fix",
|
|
4552
|
+
message: failure,
|
|
4553
|
+
headSha: evidence.headSha || null
|
|
4554
|
+
});
|
|
4555
|
+
}
|
|
4556
|
+
for (const pendingCheck of evidence.pendingChecks) {
|
|
4557
|
+
addReason({
|
|
4558
|
+
code: "check_pending",
|
|
4559
|
+
reasonClass: "pending",
|
|
4560
|
+
surface: "ci",
|
|
4561
|
+
suggestedAction: "wait",
|
|
4562
|
+
message: pendingCheck,
|
|
4563
|
+
headSha: evidence.headSha || null
|
|
4564
|
+
});
|
|
4326
4565
|
}
|
|
4327
4566
|
const reviewDecision = String(evidence.reviewDecision ?? "").toUpperCase();
|
|
4328
4567
|
if (reviewDecision === "CHANGES_REQUESTED" || reviewDecision === "REVIEW_REQUIRED") {
|
|
4329
|
-
|
|
4568
|
+
addReason({
|
|
4569
|
+
code: "review_decision_blocking",
|
|
4570
|
+
reasonClass: "reject",
|
|
4571
|
+
surface: "review",
|
|
4572
|
+
suggestedAction: "fix",
|
|
4573
|
+
message: `Required review is unresolved (${evidence.reviewDecision}).`,
|
|
4574
|
+
headSha: evidence.headSha || null
|
|
4575
|
+
});
|
|
4576
|
+
}
|
|
4577
|
+
for (const thread of unresolvedThreadSummaries(evidence.reviewThreads)) {
|
|
4578
|
+
addReason({
|
|
4579
|
+
code: "review_thread_unresolved",
|
|
4580
|
+
reasonClass: "reject",
|
|
4581
|
+
surface: "review",
|
|
4582
|
+
suggestedAction: "fix",
|
|
4583
|
+
message: thread,
|
|
4584
|
+
headSha: evidence.headSha || null
|
|
4585
|
+
});
|
|
4586
|
+
}
|
|
4587
|
+
if (greptile.mapping === "missing") {
|
|
4588
|
+
addReason({
|
|
4589
|
+
code: "greptile_missing",
|
|
4590
|
+
reasonClass: "pending",
|
|
4591
|
+
surface: "greptile",
|
|
4592
|
+
suggestedAction: "wait",
|
|
4593
|
+
message: "Missing Greptile check/review evidence for this PR.",
|
|
4594
|
+
headSha: evidence.headSha || null
|
|
4595
|
+
});
|
|
4330
4596
|
}
|
|
4331
|
-
const unresolvedThreads = unresolvedThreadSummaries(evidence.reviewThreads);
|
|
4332
|
-
if (unresolvedThreads.length > 0)
|
|
4333
|
-
reasons.push(...unresolvedThreads);
|
|
4334
|
-
const greptile = evidence.greptile;
|
|
4335
|
-
if (greptile.mapping === "missing")
|
|
4336
|
-
reasons.push("Missing Greptile check/review evidence for this PR.");
|
|
4337
|
-
const staleSignal = greptile.signals.find((signal) => signal.reviewedSha && signal.reviewedSha !== evidence.headSha);
|
|
4338
4597
|
if (greptile.mapping === "stale" || greptile.reviewedSha && greptile.reviewedSha !== evidence.headSha || !greptile.approved && staleSignal) {
|
|
4339
|
-
|
|
4598
|
+
addReason({
|
|
4599
|
+
code: "greptile_stale",
|
|
4600
|
+
reasonClass: "pending",
|
|
4601
|
+
surface: "greptile",
|
|
4602
|
+
suggestedAction: "wait",
|
|
4603
|
+
message: `Greptile evidence is stale (reviewed ${greptile.reviewedSha ?? staleSignal?.reviewedSha ?? "unknown"}, current ${evidence.headSha || "unknown"}).`,
|
|
4604
|
+
headSha: evidence.headSha || null,
|
|
4605
|
+
reviewedSha: greptile.reviewedSha ?? staleSignal?.reviewedSha ?? null
|
|
4606
|
+
});
|
|
4607
|
+
}
|
|
4608
|
+
for (const signal of pendingGreptileApiSignals) {
|
|
4609
|
+
addReason({
|
|
4610
|
+
code: "greptile_pending",
|
|
4611
|
+
reasonClass: "pending",
|
|
4612
|
+
surface: "greptile",
|
|
4613
|
+
suggestedAction: "wait",
|
|
4614
|
+
message: `Greptile API/MCP review is pending for the current PR head${signal.bodyExcerpt ? `: ${signal.bodyExcerpt}` : "."}`,
|
|
4615
|
+
headSha: evidence.headSha || null,
|
|
4616
|
+
reviewedSha: signal.reviewedSha ?? null
|
|
4617
|
+
});
|
|
4618
|
+
}
|
|
4619
|
+
for (const signal of unknownGreptileApiSignals) {
|
|
4620
|
+
addReason({
|
|
4621
|
+
code: "greptile_api_status_unknown",
|
|
4622
|
+
reasonClass: "reject",
|
|
4623
|
+
surface: "greptile",
|
|
4624
|
+
suggestedAction: "needs_attention",
|
|
4625
|
+
message: `Greptile API/MCP review status is unknown; merge requires a known terminal APPROVED/COMPLETED 5/5 result or a known conservative status${signal.bodyExcerpt ? `: ${signal.bodyExcerpt}` : "."}`,
|
|
4626
|
+
headSha: evidence.headSha || null,
|
|
4627
|
+
reviewedSha: signal.reviewedSha ?? null
|
|
4628
|
+
});
|
|
4340
4629
|
}
|
|
4341
4630
|
if (!greptile.completed) {
|
|
4342
|
-
|
|
4343
|
-
|
|
4631
|
+
addReason({
|
|
4632
|
+
code: "greptile_pending",
|
|
4633
|
+
reasonClass: "pending",
|
|
4634
|
+
surface: "greptile",
|
|
4635
|
+
suggestedAction: "wait",
|
|
4636
|
+
message: "Greptile check/review has not completed for the current PR head.",
|
|
4637
|
+
headSha: evidence.headSha || null,
|
|
4638
|
+
reviewedSha: greptile.reviewedSha ?? null
|
|
4639
|
+
});
|
|
4640
|
+
}
|
|
4641
|
+
if (!greptile.fresh) {
|
|
4642
|
+
addReason({
|
|
4643
|
+
code: "greptile_not_current_head",
|
|
4644
|
+
reasonClass: awaitingFreshGreptileProof ? "pending" : "reject",
|
|
4645
|
+
surface: "greptile",
|
|
4646
|
+
suggestedAction: awaitingFreshGreptileProof ? "wait" : "ask_greptile",
|
|
4647
|
+
message: "Greptile approval is not tied to the current PR head SHA.",
|
|
4648
|
+
headSha: evidence.headSha || null,
|
|
4649
|
+
reviewedSha: greptile.reviewedSha ?? null
|
|
4650
|
+
});
|
|
4344
4651
|
}
|
|
4345
|
-
if (!greptile.fresh)
|
|
4346
|
-
reasons.push("Greptile approval is not tied to the current PR head SHA.");
|
|
4347
4652
|
if (greptile.score && !(greptile.score.scale === 5 && greptile.score.value === 5)) {
|
|
4348
|
-
|
|
4653
|
+
addReason({
|
|
4654
|
+
code: "greptile_score_not_5",
|
|
4655
|
+
reasonClass: "reject",
|
|
4656
|
+
surface: "greptile",
|
|
4657
|
+
suggestedAction: "fix",
|
|
4658
|
+
message: `Greptile score is ${greptile.score.value}/${greptile.score.scale}; strict merge requires trusted current-head 5/5.`,
|
|
4659
|
+
headSha: evidence.headSha || null,
|
|
4660
|
+
reviewedSha: greptile.reviewedSha ?? null
|
|
4661
|
+
});
|
|
4349
4662
|
}
|
|
4350
|
-
|
|
4351
|
-
|
|
4663
|
+
const hasApprovedMapping = greptile.mapping === "score-5-of-5" || greptile.mapping === "explicit-approved";
|
|
4664
|
+
if (!greptile.score && !hasApprovedMapping) {
|
|
4665
|
+
addReason({
|
|
4666
|
+
code: "greptile_score_missing",
|
|
4667
|
+
reasonClass: awaitingFreshGreptileProof ? "pending" : "reject",
|
|
4668
|
+
surface: "greptile",
|
|
4669
|
+
suggestedAction: awaitingFreshGreptileProof ? "wait" : "ask_greptile",
|
|
4670
|
+
message: "No parseable Greptile 5/5 score or direct current-head Greptile API APPROVED mapping was found from trusted evidence; merge is blocked.",
|
|
4671
|
+
headSha: evidence.headSha || null,
|
|
4672
|
+
reviewedSha: greptile.reviewedSha ?? null
|
|
4673
|
+
});
|
|
4352
4674
|
}
|
|
4353
4675
|
if (greptile.mapping === "unproven") {
|
|
4354
|
-
|
|
4676
|
+
addReason({
|
|
4677
|
+
code: "greptile_mapping_unproven",
|
|
4678
|
+
reasonClass: awaitingFreshGreptileProof ? "pending" : "reject",
|
|
4679
|
+
surface: "greptile",
|
|
4680
|
+
suggestedAction: awaitingFreshGreptileProof ? "wait" : "ask_greptile",
|
|
4681
|
+
message: "Greptile approval mapping is unproven; PR body/title or a green check alone cannot approve merge.",
|
|
4682
|
+
headSha: evidence.headSha || null,
|
|
4683
|
+
reviewedSha: greptile.reviewedSha ?? null
|
|
4684
|
+
});
|
|
4355
4685
|
}
|
|
4356
|
-
|
|
4357
|
-
|
|
4686
|
+
for (const blocker of greptile.blockers) {
|
|
4687
|
+
addReason({
|
|
4688
|
+
code: "greptile_blocker_text",
|
|
4689
|
+
reasonClass: "reject",
|
|
4690
|
+
surface: "greptile",
|
|
4691
|
+
suggestedAction: "fix",
|
|
4692
|
+
message: `Greptile/blocker text: ${blocker}`,
|
|
4693
|
+
headSha: evidence.headSha || null,
|
|
4694
|
+
reviewedSha: greptile.reviewedSha ?? null
|
|
4695
|
+
});
|
|
4696
|
+
}
|
|
4697
|
+
for (const comment of greptile.unresolvedComments) {
|
|
4698
|
+
addReason({
|
|
4699
|
+
code: "greptile_unresolved_comment",
|
|
4700
|
+
reasonClass: "reject",
|
|
4701
|
+
surface: "greptile",
|
|
4702
|
+
suggestedAction: "fix",
|
|
4703
|
+
message: comment,
|
|
4704
|
+
headSha: evidence.headSha || null,
|
|
4705
|
+
reviewedSha: greptile.reviewedSha ?? null
|
|
4706
|
+
});
|
|
4358
4707
|
}
|
|
4359
|
-
if (greptile.unresolvedComments.length > 0)
|
|
4360
|
-
reasons.push(...greptile.unresolvedComments);
|
|
4361
4708
|
if (!greptile.approved)
|
|
4362
4709
|
warnings.push(`Greptile approval mapping is ${greptile.mapping}.`);
|
|
4363
|
-
|
|
4710
|
+
const pending = reasonDetails.length > 0 && reasonDetails.every((reason) => reason.reasonClass === "pending");
|
|
4711
|
+
return { reasons: reasonDetails.map((reason) => reason.message), reasonDetails, warnings, pending };
|
|
4364
4712
|
}
|
|
4365
4713
|
function evaluateStrictPrMergeGate(evidence) {
|
|
4366
4714
|
const evaluated = evaluateEvidence(evidence);
|
|
4367
|
-
const approved = evaluated.
|
|
4715
|
+
const approved = evaluated.reasonDetails.length === 0 && evidence.greptile.approved;
|
|
4368
4716
|
return {
|
|
4369
4717
|
approved,
|
|
4370
4718
|
pending: evaluated.pending,
|
|
4371
4719
|
reasons: evaluated.reasons,
|
|
4720
|
+
reasonDetails: evaluated.reasonDetails,
|
|
4372
4721
|
warnings: evaluated.warnings,
|
|
4373
4722
|
actionableFeedback: evaluated.reasons,
|
|
4374
4723
|
evidence
|
|
@@ -5172,7 +5521,7 @@ async function runGreptileReviewForPr(options) {
|
|
|
5172
5521
|
};
|
|
5173
5522
|
}
|
|
5174
5523
|
const blockerScanBody = stripHtml(reviewBody).replace(/\b(?:no|without|zero)\s+blockers?\b/gi, " ").replace(/\bno\s+changes\s+requested\b/gi, " ");
|
|
5175
|
-
if (/not safe(?: to merge)?|unsafe(?: to merge)?|do not merge|cannot merge|blockers?|must fix|changes requested|please fix|needs? fix|fix this|address this/i.test(blockerScanBody)) {
|
|
5524
|
+
if (/not safe(?: to merge)?|unsafe(?: to merge)?|do not merge|cannot merge|blockers?|must fix|changes requested|please fix|needs? fix|fix this|address this|\breject(?:ed|ion)?\b|\bskip(?:ped)?\b|status\s*:\s*(?:reject(?:ed)?|skip(?:ped)?|failed)/i.test(blockerScanBody)) {
|
|
5176
5525
|
reasons.push(`[AI Review] ${repoName}#${prNumber} summary indicates the PR is not safe to merge.`);
|
|
5177
5526
|
return {
|
|
5178
5527
|
verdict: "REJECT",
|
|
@@ -5254,6 +5603,7 @@ async function runGreptileReviewForPr(options) {
|
|
|
5254
5603
|
approved: strictGate.approved,
|
|
5255
5604
|
pending: strictGate.pending,
|
|
5256
5605
|
reasons: strictGate.reasons,
|
|
5606
|
+
reasonDetails: strictGate.reasonDetails,
|
|
5257
5607
|
warnings: strictGate.warnings,
|
|
5258
5608
|
greptile: strictGate.evidence.greptile,
|
|
5259
5609
|
readErrors: strictGate.evidence.readErrors
|
|
@@ -5276,6 +5626,7 @@ async function runGreptileReviewForPr(options) {
|
|
|
5276
5626
|
approved: strictGate.approved,
|
|
5277
5627
|
pending: strictGate.pending,
|
|
5278
5628
|
reasons: strictGate.reasons,
|
|
5629
|
+
reasonDetails: strictGate.reasonDetails,
|
|
5279
5630
|
warnings: strictGate.warnings,
|
|
5280
5631
|
greptile: strictGate.evidence.greptile,
|
|
5281
5632
|
readErrors: strictGate.evidence.readErrors
|
|
@@ -5402,6 +5753,7 @@ async function runGithubGreptileFallbackReviewForPr(options) {
|
|
|
5402
5753
|
approved: strictGate.approved,
|
|
5403
5754
|
pending: strictGate.pending,
|
|
5404
5755
|
reasons: strictGate.reasons,
|
|
5756
|
+
reasonDetails: strictGate.reasonDetails,
|
|
5405
5757
|
warnings: strictGate.warnings,
|
|
5406
5758
|
greptile: strictGate.evidence.greptile
|
|
5407
5759
|
},
|
|
@@ -5424,6 +5776,7 @@ async function runGithubGreptileFallbackReviewForPr(options) {
|
|
|
5424
5776
|
approved: strictGate.approved,
|
|
5425
5777
|
pending: strictGate.pending,
|
|
5426
5778
|
reasons: strictGate.reasons,
|
|
5779
|
+
reasonDetails: strictGate.reasonDetails,
|
|
5427
5780
|
warnings: strictGate.warnings,
|
|
5428
5781
|
greptile: strictGate.evidence.greptile
|
|
5429
5782
|
},
|
|
@@ -5539,19 +5892,25 @@ function shouldTriggerGreptileReview(existingReview, expectedHeadSha) {
|
|
|
5539
5892
|
if ((existingReview.metadata?.checkHeadSha || "") !== expectedHeadSha) {
|
|
5540
5893
|
return true;
|
|
5541
5894
|
}
|
|
5542
|
-
return
|
|
5895
|
+
return false;
|
|
5543
5896
|
}
|
|
5544
5897
|
function shouldContinueGreptileMcpPolling(options) {
|
|
5545
5898
|
if (options.githubCheckState.completed) {
|
|
5546
5899
|
return false;
|
|
5547
5900
|
}
|
|
5901
|
+
if (options.attempt + 1 >= options.pollAttempts) {
|
|
5902
|
+
return false;
|
|
5903
|
+
}
|
|
5548
5904
|
if (options.selectedReview && !isGreptileReviewTerminal(options.selectedReview.status)) {
|
|
5549
5905
|
return true;
|
|
5550
5906
|
}
|
|
5551
|
-
return
|
|
5907
|
+
return true;
|
|
5552
5908
|
}
|
|
5553
5909
|
function shouldContinueGithubGreptileFallbackPolling(options) {
|
|
5554
5910
|
const waitingForVisiblePendingReview = options.checkState.pending && (!options.fallbackReview || !options.selectedReview && !options.approvedViaReviewedAncestor);
|
|
5911
|
+
if (options.attempt + 1 >= options.pollAttempts) {
|
|
5912
|
+
return false;
|
|
5913
|
+
}
|
|
5555
5914
|
if (waitingForVisiblePendingReview) {
|
|
5556
5915
|
return true;
|
|
5557
5916
|
}
|
|
@@ -7003,7 +7362,7 @@ function gitOpenPr(options) {
|
|
|
7003
7362
|
"",
|
|
7004
7363
|
"## Review",
|
|
7005
7364
|
"- Completion verification will run validation, verifier review, and PR policy checks.",
|
|
7006
|
-
"- When repository policy allows it, Rig
|
|
7365
|
+
"- When repository policy allows it, Rig attempts an immediate strict-gated, head-locked merge after approval."
|
|
7007
7366
|
].join(`
|
|
7008
7367
|
`);
|
|
7009
7368
|
const preCheck = runCapture2(withGhRepo([gh, "pr", "list", "--state", "merged", "--head", branch, "--json", "url,mergedAt", "--jq", ".[0]"], repoNameWithOwner), repoRoot);
|