@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
|
@@ -3720,7 +3720,7 @@ function stripHtml(input) {
|
|
|
3720
3720
|
}
|
|
3721
3721
|
function containsBlockerText(input) {
|
|
3722
3722
|
const text = stripHtml(input).replace(/\b(?:no|without|zero)\s+blockers?\b/gi, " ").replace(/\bno\s+changes\s+requested\b/gi, " ");
|
|
3723
|
-
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);
|
|
3723
|
+
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);
|
|
3724
3724
|
}
|
|
3725
3725
|
function isStrictFiveOfFive(score) {
|
|
3726
3726
|
return score.value === 5 && score.scale === 5;
|
|
@@ -3728,6 +3728,189 @@ function isStrictFiveOfFive(score) {
|
|
|
3728
3728
|
function containsConflictingScoreText(input) {
|
|
3729
3729
|
return parseGreptileScores(input).some((score) => !isStrictFiveOfFive(score));
|
|
3730
3730
|
}
|
|
3731
|
+
function greptileStatusVerdict(status) {
|
|
3732
|
+
const normalized = String(status ?? "").trim().toUpperCase().replace(/[\s-]+/g, "_");
|
|
3733
|
+
if (!normalized)
|
|
3734
|
+
return null;
|
|
3735
|
+
if (["APPROVE", "APPROVED"].includes(normalized))
|
|
3736
|
+
return "approved";
|
|
3737
|
+
if (["REJECT", "REJECTED", "CHANGES_REQUESTED", "CHANGE_REQUESTED"].includes(normalized))
|
|
3738
|
+
return "rejected";
|
|
3739
|
+
if (["SKIP", "SKIPPED"].includes(normalized))
|
|
3740
|
+
return "skipped";
|
|
3741
|
+
if (["FAIL", "FAILED", "FAILURE", "ERROR"].includes(normalized))
|
|
3742
|
+
return "failed";
|
|
3743
|
+
if (["PENDING", "QUEUED", "IN_PROGRESS", "RUNNING", "STARTED", "REQUESTED", "REVIEWING_FILES", "GENERATING_SUMMARY"].includes(normalized))
|
|
3744
|
+
return "pending";
|
|
3745
|
+
if (["COMPLETE", "COMPLETED"].includes(normalized))
|
|
3746
|
+
return "completed";
|
|
3747
|
+
return null;
|
|
3748
|
+
}
|
|
3749
|
+
function isBlockingGreptileVerdict(verdict) {
|
|
3750
|
+
return verdict === "rejected" || verdict === "skipped" || verdict === "failed";
|
|
3751
|
+
}
|
|
3752
|
+
function greptileRequestTimeoutMs(env) {
|
|
3753
|
+
const fallback = 30000;
|
|
3754
|
+
const parsed = Number.parseInt(env.GREPTILE_REQUEST_TIMEOUT_MS || `${fallback}`, 10);
|
|
3755
|
+
return Number.isFinite(parsed) && parsed >= 1000 ? parsed : fallback;
|
|
3756
|
+
}
|
|
3757
|
+
function normalizeGreptileMcpCodeReview(entry, fallbackId) {
|
|
3758
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
3759
|
+
return null;
|
|
3760
|
+
const record = entry;
|
|
3761
|
+
const id = typeof record.id === "string" ? record.id.trim() : fallbackId?.trim() ?? "";
|
|
3762
|
+
if (!id)
|
|
3763
|
+
return null;
|
|
3764
|
+
const metadataRecord = record.metadata && typeof record.metadata === "object" && !Array.isArray(record.metadata) ? record.metadata : null;
|
|
3765
|
+
return {
|
|
3766
|
+
id,
|
|
3767
|
+
status: typeof record.status === "string" ? record.status : null,
|
|
3768
|
+
createdAt: typeof record.createdAt === "string" ? record.createdAt : null,
|
|
3769
|
+
body: typeof record.body === "string" ? record.body : null,
|
|
3770
|
+
metadata: metadataRecord ? { checkHeadSha: typeof metadataRecord.checkHeadSha === "string" ? metadataRecord.checkHeadSha : null } : null
|
|
3771
|
+
};
|
|
3772
|
+
}
|
|
3773
|
+
function uniqueGreptileCodeReviews(reviews) {
|
|
3774
|
+
const seen = new Set;
|
|
3775
|
+
const unique2 = [];
|
|
3776
|
+
for (const review of reviews) {
|
|
3777
|
+
if (seen.has(review.id))
|
|
3778
|
+
continue;
|
|
3779
|
+
seen.add(review.id);
|
|
3780
|
+
unique2.push(review);
|
|
3781
|
+
}
|
|
3782
|
+
return unique2;
|
|
3783
|
+
}
|
|
3784
|
+
function selectGreptileApiReviewsForGate(reviews, headSha) {
|
|
3785
|
+
const sorted = [...reviews].sort((left, right) => Date.parse(right.createdAt ?? "") - Date.parse(left.createdAt ?? ""));
|
|
3786
|
+
const current = headSha ? sorted.filter((review) => review.metadata?.checkHeadSha === headSha) : [];
|
|
3787
|
+
const untied = sorted.filter((review) => !review.metadata?.checkHeadSha);
|
|
3788
|
+
const latest = sorted.slice(0, 1);
|
|
3789
|
+
return uniqueGreptileCodeReviews([...current, ...untied, ...latest]);
|
|
3790
|
+
}
|
|
3791
|
+
function greptileApiSignalFromCodeReview(review, details) {
|
|
3792
|
+
const selected = details ?? review;
|
|
3793
|
+
return {
|
|
3794
|
+
id: selected.id || review.id,
|
|
3795
|
+
body: selected.body ?? review.body ?? null,
|
|
3796
|
+
reviewedSha: selected.metadata?.checkHeadSha ?? review.metadata?.checkHeadSha ?? null,
|
|
3797
|
+
status: selected.status ?? review.status ?? null
|
|
3798
|
+
};
|
|
3799
|
+
}
|
|
3800
|
+
async function callGreptileMcpToolForGate(input) {
|
|
3801
|
+
const controller = new AbortController;
|
|
3802
|
+
const timeoutId = setTimeout(() => {
|
|
3803
|
+
controller.abort(new Error(`Greptile MCP tool ${input.name} timed out after ${input.timeoutMs}ms.`));
|
|
3804
|
+
}, input.timeoutMs);
|
|
3805
|
+
let response;
|
|
3806
|
+
try {
|
|
3807
|
+
response = await input.fetchFn(input.apiBase, {
|
|
3808
|
+
method: "POST",
|
|
3809
|
+
headers: {
|
|
3810
|
+
Authorization: `Bearer ${input.apiKey}`,
|
|
3811
|
+
"Content-Type": "application/json"
|
|
3812
|
+
},
|
|
3813
|
+
body: JSON.stringify({
|
|
3814
|
+
jsonrpc: "2.0",
|
|
3815
|
+
id: `rig-strict-gate-${input.name}-${Date.now()}`,
|
|
3816
|
+
method: "tools/call",
|
|
3817
|
+
params: { name: input.name, arguments: input.args }
|
|
3818
|
+
}),
|
|
3819
|
+
signal: controller.signal
|
|
3820
|
+
});
|
|
3821
|
+
} catch (error) {
|
|
3822
|
+
if (controller.signal.aborted) {
|
|
3823
|
+
throw controller.signal.reason instanceof Error ? controller.signal.reason : new Error(`Greptile MCP tool ${input.name} timed out after ${input.timeoutMs}ms.`);
|
|
3824
|
+
}
|
|
3825
|
+
throw error;
|
|
3826
|
+
} finally {
|
|
3827
|
+
clearTimeout(timeoutId);
|
|
3828
|
+
}
|
|
3829
|
+
const raw = await response.text();
|
|
3830
|
+
if (!response.ok) {
|
|
3831
|
+
throw new Error(`HTTP ${response.status}: ${raw}`);
|
|
3832
|
+
}
|
|
3833
|
+
let envelope;
|
|
3834
|
+
try {
|
|
3835
|
+
envelope = JSON.parse(raw);
|
|
3836
|
+
} catch {
|
|
3837
|
+
throw new Error(`Malformed MCP response: ${raw}`);
|
|
3838
|
+
}
|
|
3839
|
+
if (envelope.error?.message) {
|
|
3840
|
+
throw new Error(envelope.error.message);
|
|
3841
|
+
}
|
|
3842
|
+
const text = (envelope.result?.content ?? []).filter((item) => item.type === "text" && typeof item.text === "string").map((item) => item.text ?? "").join(`
|
|
3843
|
+
`).trim();
|
|
3844
|
+
if (!text) {
|
|
3845
|
+
throw new Error(`MCP tool ${input.name} returned no text payload.`);
|
|
3846
|
+
}
|
|
3847
|
+
return text;
|
|
3848
|
+
}
|
|
3849
|
+
async function callGreptileMcpToolJsonForGate(input) {
|
|
3850
|
+
const text = await callGreptileMcpToolForGate(input);
|
|
3851
|
+
try {
|
|
3852
|
+
return JSON.parse(text);
|
|
3853
|
+
} catch {
|
|
3854
|
+
throw new Error(`MCP tool ${input.name} returned malformed JSON: ${text}`);
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
async function collectConfiguredGreptileApiSignals(input) {
|
|
3858
|
+
if (!input.enabled || input.options?.enabled === false) {
|
|
3859
|
+
return { signals: [], errors: [] };
|
|
3860
|
+
}
|
|
3861
|
+
const env = input.options?.env ?? process.env;
|
|
3862
|
+
const secrets = resolveRuntimeSecrets(env);
|
|
3863
|
+
const apiKey = secrets.GREPTILE_API_KEY?.trim() ?? "";
|
|
3864
|
+
if (!apiKey) {
|
|
3865
|
+
return { signals: [], errors: [] };
|
|
3866
|
+
}
|
|
3867
|
+
const fetchFn = input.options?.fetch ?? globalThis.fetch;
|
|
3868
|
+
if (typeof fetchFn !== "function") {
|
|
3869
|
+
return { signals: [], errors: ["Greptile API/MCP evidence read failed: fetch is not available."] };
|
|
3870
|
+
}
|
|
3871
|
+
const apiBase = secrets.GREPTILE_API_BASE?.trim() || "https://api.greptile.com/mcp";
|
|
3872
|
+
const remote = secrets.GREPTILE_REMOTE?.trim() || "github";
|
|
3873
|
+
const repository = secrets.GREPTILE_REPOSITORY?.trim() || input.repoName;
|
|
3874
|
+
const defaultBranch = secrets.GREPTILE_DEFAULT_BRANCH?.trim() || input.baseRefName?.trim() || "main";
|
|
3875
|
+
const timeoutMs = greptileRequestTimeoutMs(env);
|
|
3876
|
+
try {
|
|
3877
|
+
const listPayload = await callGreptileMcpToolJsonForGate({
|
|
3878
|
+
apiBase,
|
|
3879
|
+
apiKey,
|
|
3880
|
+
name: "list_code_reviews",
|
|
3881
|
+
args: {
|
|
3882
|
+
name: repository,
|
|
3883
|
+
remote,
|
|
3884
|
+
defaultBranch,
|
|
3885
|
+
prNumber: input.prNumber,
|
|
3886
|
+
limit: 20
|
|
3887
|
+
},
|
|
3888
|
+
timeoutMs,
|
|
3889
|
+
fetchFn
|
|
3890
|
+
});
|
|
3891
|
+
const reviews = (listPayload.codeReviews ?? []).map((entry) => normalizeGreptileMcpCodeReview(entry)).filter((review) => !!review);
|
|
3892
|
+
const selectedReviews = selectGreptileApiReviewsForGate(reviews, input.headSha);
|
|
3893
|
+
const signals = [];
|
|
3894
|
+
for (const review of selectedReviews) {
|
|
3895
|
+
const detailsPayload = await callGreptileMcpToolJsonForGate({
|
|
3896
|
+
apiBase,
|
|
3897
|
+
apiKey,
|
|
3898
|
+
name: "get_code_review",
|
|
3899
|
+
args: { codeReviewId: review.id },
|
|
3900
|
+
timeoutMs,
|
|
3901
|
+
fetchFn
|
|
3902
|
+
});
|
|
3903
|
+
const details = normalizeGreptileMcpCodeReview(detailsPayload.codeReview, review.id) ?? review;
|
|
3904
|
+
signals.push(greptileApiSignalFromCodeReview(review, details));
|
|
3905
|
+
}
|
|
3906
|
+
return { signals, errors: [] };
|
|
3907
|
+
} catch (error) {
|
|
3908
|
+
return {
|
|
3909
|
+
signals: [],
|
|
3910
|
+
errors: [`Greptile API/MCP evidence read failed: ${error instanceof Error ? error.message : String(error)}`]
|
|
3911
|
+
};
|
|
3912
|
+
}
|
|
3913
|
+
}
|
|
3731
3914
|
function firstString(record, keys) {
|
|
3732
3915
|
for (const key of keys) {
|
|
3733
3916
|
const value = record[key];
|
|
@@ -3854,7 +4037,7 @@ function normalizeReviewThread(entry) {
|
|
|
3854
4037
|
function relevantIssueComment(comment) {
|
|
3855
4038
|
const login = comment.user?.login ?? comment.author?.login ?? "";
|
|
3856
4039
|
const body = comment.body ?? "";
|
|
3857
|
-
return isGreptileGithubLogin(login) || /greptile|
|
|
4040
|
+
return isGreptileGithubLogin(login) || containsBlockerText(body) || /greptile|score|confidence|\b\d+\s*\/\s*5\b/i.test(body);
|
|
3858
4041
|
}
|
|
3859
4042
|
function latestThreadComment(thread) {
|
|
3860
4043
|
const nodes = thread.comments?.nodes ?? [];
|
|
@@ -3890,7 +4073,8 @@ function makeGreptileSignal(input) {
|
|
|
3890
4073
|
const scores = parseGreptileScores(input.body);
|
|
3891
4074
|
const reviewedSha = input.reviewedSha?.trim() || null;
|
|
3892
4075
|
const current = reviewedSha ? reviewedSha === input.currentHeadSha : null;
|
|
3893
|
-
const
|
|
4076
|
+
const verdict = input.verdict ?? null;
|
|
4077
|
+
const blocker = input.blocker ?? (isBlockingGreptileVerdict(verdict) || containsBlockerText(input.body));
|
|
3894
4078
|
const explicitApproval = input.explicitApproval ?? false;
|
|
3895
4079
|
return {
|
|
3896
4080
|
source: input.source,
|
|
@@ -3902,6 +4086,7 @@ function makeGreptileSignal(input) {
|
|
|
3902
4086
|
score: scores[0] ?? null,
|
|
3903
4087
|
scores,
|
|
3904
4088
|
explicitApproval,
|
|
4089
|
+
verdict,
|
|
3905
4090
|
blocker,
|
|
3906
4091
|
actionable: input.actionable ?? blocker,
|
|
3907
4092
|
bodyExcerpt: bodyExcerpt(input.body),
|
|
@@ -3924,9 +4109,9 @@ function collectGreptileSignals(evidence) {
|
|
|
3924
4109
|
for (const context of contextSources) {
|
|
3925
4110
|
if (!context.body.trim())
|
|
3926
4111
|
continue;
|
|
3927
|
-
if (!/greptile|score|confidence|\b\d+\s*\/\s*5\b|blocker|unsafe|not safe|do not merge|changes requested/i.test(context.body))
|
|
3928
|
-
continue;
|
|
3929
4112
|
const contextBlocker = containsBlockerText(context.body);
|
|
4113
|
+
if (!contextBlocker && !/greptile|score|confidence|\b\d+\s*\/\s*5\b/i.test(context.body))
|
|
4114
|
+
continue;
|
|
3930
4115
|
signals.push(makeGreptileSignal({
|
|
3931
4116
|
source: context.source,
|
|
3932
4117
|
body: context.body,
|
|
@@ -3939,16 +4124,16 @@ function collectGreptileSignals(evidence) {
|
|
|
3939
4124
|
for (const apiSignal of evidence.apiSignals ?? []) {
|
|
3940
4125
|
const body = [apiSignal.status ? `Status: ${apiSignal.status}` : "", apiSignal.body ?? ""].filter(Boolean).join(`
|
|
3941
4126
|
|
|
3942
|
-
`);
|
|
3943
|
-
|
|
3944
|
-
continue;
|
|
4127
|
+
`) || "Status: UNKNOWN";
|
|
4128
|
+
const verdict = greptileStatusVerdict(apiSignal.status);
|
|
3945
4129
|
signals.push(makeGreptileSignal({
|
|
3946
4130
|
source: "api",
|
|
3947
4131
|
body,
|
|
3948
4132
|
currentHeadSha: evidence.currentHeadSha,
|
|
3949
4133
|
trusted: true,
|
|
3950
4134
|
reviewedSha: apiSignal.reviewedSha ?? null,
|
|
3951
|
-
explicitApproval:
|
|
4135
|
+
explicitApproval: verdict === "approved",
|
|
4136
|
+
verdict
|
|
3952
4137
|
}));
|
|
3953
4138
|
}
|
|
3954
4139
|
for (const review of evidence.reviews) {
|
|
@@ -3973,20 +4158,6 @@ function collectGreptileSignals(evidence) {
|
|
|
3973
4158
|
blocker: state === "CHANGES_REQUESTED" || undefined
|
|
3974
4159
|
}));
|
|
3975
4160
|
}
|
|
3976
|
-
for (const comment of evidence.changedFileReviewComments) {
|
|
3977
|
-
const login = commentAuthorLogin(comment);
|
|
3978
|
-
const body = comment.body ?? "";
|
|
3979
|
-
if (!body.trim() || !isGreptileGithubLogin(login))
|
|
3980
|
-
continue;
|
|
3981
|
-
signals.push(makeGreptileSignal({
|
|
3982
|
-
source: "changed-file-comment",
|
|
3983
|
-
body,
|
|
3984
|
-
currentHeadSha: evidence.currentHeadSha,
|
|
3985
|
-
trusted: true,
|
|
3986
|
-
authorLogin: login,
|
|
3987
|
-
reviewedSha: comment.commit_id ?? comment.original_commit_id ?? null
|
|
3988
|
-
}));
|
|
3989
|
-
}
|
|
3990
4161
|
for (const comment of evidence.relevantIssueComments) {
|
|
3991
4162
|
const login = commentAuthorLogin(comment);
|
|
3992
4163
|
const body = comment.body ?? "";
|
|
@@ -4094,10 +4265,17 @@ function deriveGreptileEvidence(input) {
|
|
|
4094
4265
|
const isCurrentOrUntied = (signal) => !signal.reviewedSha || signal.reviewedSha === input.currentHeadSha;
|
|
4095
4266
|
const currentOrUntiedScoreEntries = allScoreEntries.filter((entry) => isCurrentOrUntied(entry.signal));
|
|
4096
4267
|
const lowScoreEntries = currentOrUntiedScoreEntries.filter((entry) => !isStrictFiveOfFive(entry.score));
|
|
4097
|
-
const
|
|
4268
|
+
const currentPendingApiSignals = trustedSignals.filter((signal) => signal.source === "api" && signal.verdict === "pending" && isCurrentOrUntied(signal));
|
|
4269
|
+
const signalCanApproveByScore = (signal) => {
|
|
4270
|
+
if (signal.source === "api")
|
|
4271
|
+
return signal.verdict === "approved" || signal.verdict === "completed";
|
|
4272
|
+
return signal.verdict !== "pending" && !isBlockingGreptileVerdict(signal.verdict);
|
|
4273
|
+
};
|
|
4274
|
+
const approvingScoreEntry = trustedScoreEntries.find((entry) => entry.signal.reviewedSha === input.currentHeadSha && entry.signal.source !== "github-check" && signalCanApproveByScore(entry.signal) && isStrictFiveOfFive(entry.score)) ?? null;
|
|
4275
|
+
const approvingExplicitSignal = trustedSignals.find((signal) => signal.source === "api" && signal.reviewedSha === input.currentHeadSha && signal.explicitApproval === true && !signal.blocker) ?? null;
|
|
4098
4276
|
const approvedByScore = !!approvingScoreEntry;
|
|
4099
|
-
const approvedByExplicitMapping =
|
|
4100
|
-
const approvingSignal = approvingScoreEntry?.signal ??
|
|
4277
|
+
const approvedByExplicitMapping = !!approvingExplicitSignal;
|
|
4278
|
+
const approvingSignal = approvingScoreEntry?.signal ?? approvingExplicitSignal;
|
|
4101
4279
|
const lowestScore = lowScoreEntries.map((entry) => entry.score).sort((left, right) => left.value - right.value)[0] ?? null;
|
|
4102
4280
|
const score = lowestScore ?? approvingScoreEntry?.score ?? trustedScoreEntries[0]?.score ?? contextScoreEntries[0]?.score ?? null;
|
|
4103
4281
|
const failedGreptileChecks = input.checks.filter((check) => isGreptileLabel(checkName(check)) && isFailingCheck(check)).map((check) => `${checkName(check)} (${checkState(check) || "failed"})`);
|
|
@@ -4126,13 +4304,14 @@ function deriveGreptileEvidence(input) {
|
|
|
4126
4304
|
const completedState = ["APPROVED", "COMMENTED", "CHANGES_REQUESTED"].includes(state) || !!review.body?.trim();
|
|
4127
4305
|
return completedState && review.commit_id === input.currentHeadSha;
|
|
4128
4306
|
});
|
|
4307
|
+
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"));
|
|
4129
4308
|
const approvalReviewedSha = approvingSignal?.reviewedSha ?? null;
|
|
4130
4309
|
const reviewedSha = approvalReviewedSha ?? staleSignals[0]?.reviewedSha ?? trustedSignals.map((signal) => signal.reviewedSha ?? null).find(Boolean) ?? null;
|
|
4131
4310
|
const fresh = !!approvalReviewedSha && approvalReviewedSha === input.currentHeadSha;
|
|
4132
|
-
const completed = completedGreptileCheck || completedGreptileReview ||
|
|
4311
|
+
const completed = completedGreptileCheck || completedGreptileReview || completedGreptileApi || !!approvingSignal;
|
|
4133
4312
|
const hasGreptileEvidence = trustedSignals.length > 0 || signals.some((signal) => /greptile/i.test(signal.body));
|
|
4134
|
-
const approved = fresh && completed && !blockers.length && !unresolvedComments.length && (approvedByScore || approvedByExplicitMapping);
|
|
4135
|
-
const mapping = !hasGreptileEvidence ? "missing" : staleSignals.length > 0 && !approvingSignal ? "stale" : approvedByScore ? "score-5-of-5" : "unproven";
|
|
4313
|
+
const approved = fresh && completed && !blockers.length && !unresolvedComments.length && currentPendingApiSignals.length === 0 && (approvedByScore || approvedByExplicitMapping);
|
|
4314
|
+
const mapping = !hasGreptileEvidence ? "missing" : staleSignals.length > 0 && !approvingSignal ? "stale" : approvedByScore ? "score-5-of-5" : approvedByExplicitMapping ? "explicit-approved" : "unproven";
|
|
4136
4315
|
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";
|
|
4137
4316
|
return {
|
|
4138
4317
|
source,
|
|
@@ -4239,6 +4418,7 @@ async function collectPrReviewEvidence(input) {
|
|
|
4239
4418
|
readErrors.push("gh pr view did not return required reviews array");
|
|
4240
4419
|
}
|
|
4241
4420
|
const headSha = firstString(view, ["headRefOid", "headSha", "head_sha"]);
|
|
4421
|
+
const baseRefName = firstString(view, ["baseRefName"]);
|
|
4242
4422
|
const statusCheckRollup = arrayField(view, "statusCheckRollup").map(normalizeStatusCheck).filter((entry) => !!entry);
|
|
4243
4423
|
const reviews = arrayField(view, "reviews").map(normalizeReview).filter((entry) => !!entry);
|
|
4244
4424
|
const reviewCommentsRead = await runJsonArray(input.command, ["api", `repos/${parsed.repoName}/pulls/${parsed.prNumber}/comments`, "--paginate", "--slurp"], input.projectRoot);
|
|
@@ -4276,8 +4456,19 @@ async function collectPrReviewEvidence(input) {
|
|
|
4276
4456
|
}
|
|
4277
4457
|
}
|
|
4278
4458
|
const checksWithGreptileDetails = [...statusCheckRollup, ...greptileCheckDetails];
|
|
4459
|
+
const shouldCollectConfiguredGreptileApi = input.greptileApi?.enabled !== false;
|
|
4460
|
+
const configuredGreptileApiRead = await collectConfiguredGreptileApiSignals({
|
|
4461
|
+
enabled: shouldCollectConfiguredGreptileApi,
|
|
4462
|
+
options: input.greptileApi,
|
|
4463
|
+
repoName: parsed.repoName,
|
|
4464
|
+
prNumber: parsed.prNumber,
|
|
4465
|
+
headSha,
|
|
4466
|
+
baseRefName
|
|
4467
|
+
});
|
|
4468
|
+
readErrors.push(...configuredGreptileApiRead.errors);
|
|
4469
|
+
const apiSignals = [...input.apiSignals ?? [], ...configuredGreptileApiRead.signals];
|
|
4279
4470
|
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})` : ""}`);
|
|
4280
|
-
const pendingChecks = statusCheckRollup.filter((check) => isPendingCheck(check) && !isAllowedFailure(checkName(check), input.allowedFailures ?? [])).map((check) => `Check pending: ${checkName(check)}`);
|
|
4471
|
+
const pendingChecks = statusCheckRollup.filter((check) => isPendingCheck(check) && (isGreptileLabel(checkName(check)) || !isAllowedFailure(checkName(check), input.allowedFailures ?? []))).map((check) => `Check pending: ${checkName(check)}`);
|
|
4281
4472
|
const evidenceBase = {
|
|
4282
4473
|
title: firstString(view, ["title"]),
|
|
4283
4474
|
body: firstString(view, ["body"]),
|
|
@@ -4287,7 +4478,7 @@ async function collectPrReviewEvidence(input) {
|
|
|
4287
4478
|
reviewThreads,
|
|
4288
4479
|
checks: checksWithGreptileDetails,
|
|
4289
4480
|
currentHeadSha: headSha,
|
|
4290
|
-
apiSignals
|
|
4481
|
+
apiSignals
|
|
4291
4482
|
};
|
|
4292
4483
|
const greptile = deriveGreptileEvidence(evidenceBase);
|
|
4293
4484
|
return {
|
|
@@ -4298,7 +4489,7 @@ async function collectPrReviewEvidence(input) {
|
|
|
4298
4489
|
body: evidenceBase.body,
|
|
4299
4490
|
headSha,
|
|
4300
4491
|
headRefName: firstString(view, ["headRefName"]),
|
|
4301
|
-
baseRefName
|
|
4492
|
+
baseRefName,
|
|
4302
4493
|
state: firstString(view, ["state"]),
|
|
4303
4494
|
isDraft: typeof view.isDraft === "boolean" ? view.isDraft : null,
|
|
4304
4495
|
mergeable: firstString(view, ["mergeable"]),
|
|
@@ -4315,66 +4506,224 @@ async function collectPrReviewEvidence(input) {
|
|
|
4315
4506
|
greptile
|
|
4316
4507
|
};
|
|
4317
4508
|
}
|
|
4509
|
+
function capGateMessage(value, maxChars = 1200) {
|
|
4510
|
+
const normalized = value.trim();
|
|
4511
|
+
return normalized.length > maxChars ? `${normalized.slice(0, maxChars)}
|
|
4512
|
+
[truncated for gate summary; see full evidence artifact]` : normalized;
|
|
4513
|
+
}
|
|
4318
4514
|
function evaluateEvidence(evidence) {
|
|
4319
|
-
const
|
|
4515
|
+
const reasonDetails = [];
|
|
4320
4516
|
const warnings = [];
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4517
|
+
const seen = new Set;
|
|
4518
|
+
const addReason = (reason) => {
|
|
4519
|
+
const capped = { ...reason, message: capGateMessage(reason.message) };
|
|
4520
|
+
const key = `${capped.code}:${capped.message}`;
|
|
4521
|
+
if (seen.has(key))
|
|
4522
|
+
return;
|
|
4523
|
+
seen.add(key);
|
|
4524
|
+
reasonDetails.push(capped);
|
|
4525
|
+
};
|
|
4526
|
+
const greptile = evidence.greptile;
|
|
4527
|
+
const staleSignal = greptile.signals.find((signal) => signal.reviewedSha && signal.reviewedSha !== evidence.headSha);
|
|
4528
|
+
const hasPendingGreptileCheck = evidence.pendingChecks.some((check) => /greptile/i.test(check));
|
|
4529
|
+
const pendingGreptileApiSignals = greptile.signals.filter((signal) => signal.source === "api" && signal.verdict === "pending" && (!signal.reviewedSha || signal.reviewedSha === evidence.headSha));
|
|
4530
|
+
const unknownGreptileApiSignals = greptile.signals.filter((signal) => signal.source === "api" && !signal.verdict && (!signal.reviewedSha || signal.reviewedSha === evidence.headSha));
|
|
4531
|
+
const awaitingFreshGreptileProof = hasPendingGreptileCheck || pendingGreptileApiSignals.length > 0 || !greptile.completed || greptile.mapping === "missing" || greptile.mapping === "stale";
|
|
4532
|
+
for (const error of evidence.readErrors) {
|
|
4533
|
+
addReason({
|
|
4534
|
+
code: "read_error",
|
|
4535
|
+
reasonClass: "reject",
|
|
4536
|
+
surface: error.startsWith("Greptile API/MCP") ? "greptile" : "github",
|
|
4537
|
+
suggestedAction: "needs_attention",
|
|
4538
|
+
message: `Required PR evidence surface could not be read completely: ${error}`,
|
|
4539
|
+
headSha: evidence.headSha || null
|
|
4540
|
+
});
|
|
4541
|
+
}
|
|
4542
|
+
if (!evidence.headSha) {
|
|
4543
|
+
addReason({
|
|
4544
|
+
code: "missing_head_sha",
|
|
4545
|
+
reasonClass: "reject",
|
|
4546
|
+
surface: "github",
|
|
4547
|
+
suggestedAction: "needs_attention",
|
|
4548
|
+
message: "PR head SHA could not be read; current-head Greptile approval cannot be proven.",
|
|
4549
|
+
headSha: null
|
|
4550
|
+
});
|
|
4551
|
+
}
|
|
4552
|
+
for (const failure of evidence.checkFailures) {
|
|
4553
|
+
addReason({
|
|
4554
|
+
code: "ci_failed",
|
|
4555
|
+
reasonClass: "reject",
|
|
4556
|
+
surface: "ci",
|
|
4557
|
+
suggestedAction: "fix",
|
|
4558
|
+
message: failure,
|
|
4559
|
+
headSha: evidence.headSha || null
|
|
4560
|
+
});
|
|
4561
|
+
}
|
|
4562
|
+
for (const pendingCheck of evidence.pendingChecks) {
|
|
4563
|
+
addReason({
|
|
4564
|
+
code: "check_pending",
|
|
4565
|
+
reasonClass: "pending",
|
|
4566
|
+
surface: "ci",
|
|
4567
|
+
suggestedAction: "wait",
|
|
4568
|
+
message: pendingCheck,
|
|
4569
|
+
headSha: evidence.headSha || null
|
|
4570
|
+
});
|
|
4332
4571
|
}
|
|
4333
4572
|
const reviewDecision = String(evidence.reviewDecision ?? "").toUpperCase();
|
|
4334
4573
|
if (reviewDecision === "CHANGES_REQUESTED" || reviewDecision === "REVIEW_REQUIRED") {
|
|
4335
|
-
|
|
4574
|
+
addReason({
|
|
4575
|
+
code: "review_decision_blocking",
|
|
4576
|
+
reasonClass: "reject",
|
|
4577
|
+
surface: "review",
|
|
4578
|
+
suggestedAction: "fix",
|
|
4579
|
+
message: `Required review is unresolved (${evidence.reviewDecision}).`,
|
|
4580
|
+
headSha: evidence.headSha || null
|
|
4581
|
+
});
|
|
4582
|
+
}
|
|
4583
|
+
for (const thread of unresolvedThreadSummaries(evidence.reviewThreads)) {
|
|
4584
|
+
addReason({
|
|
4585
|
+
code: "review_thread_unresolved",
|
|
4586
|
+
reasonClass: "reject",
|
|
4587
|
+
surface: "review",
|
|
4588
|
+
suggestedAction: "fix",
|
|
4589
|
+
message: thread,
|
|
4590
|
+
headSha: evidence.headSha || null
|
|
4591
|
+
});
|
|
4592
|
+
}
|
|
4593
|
+
if (greptile.mapping === "missing") {
|
|
4594
|
+
addReason({
|
|
4595
|
+
code: "greptile_missing",
|
|
4596
|
+
reasonClass: "pending",
|
|
4597
|
+
surface: "greptile",
|
|
4598
|
+
suggestedAction: "wait",
|
|
4599
|
+
message: "Missing Greptile check/review evidence for this PR.",
|
|
4600
|
+
headSha: evidence.headSha || null
|
|
4601
|
+
});
|
|
4336
4602
|
}
|
|
4337
|
-
const unresolvedThreads = unresolvedThreadSummaries(evidence.reviewThreads);
|
|
4338
|
-
if (unresolvedThreads.length > 0)
|
|
4339
|
-
reasons.push(...unresolvedThreads);
|
|
4340
|
-
const greptile = evidence.greptile;
|
|
4341
|
-
if (greptile.mapping === "missing")
|
|
4342
|
-
reasons.push("Missing Greptile check/review evidence for this PR.");
|
|
4343
|
-
const staleSignal = greptile.signals.find((signal) => signal.reviewedSha && signal.reviewedSha !== evidence.headSha);
|
|
4344
4603
|
if (greptile.mapping === "stale" || greptile.reviewedSha && greptile.reviewedSha !== evidence.headSha || !greptile.approved && staleSignal) {
|
|
4345
|
-
|
|
4604
|
+
addReason({
|
|
4605
|
+
code: "greptile_stale",
|
|
4606
|
+
reasonClass: "pending",
|
|
4607
|
+
surface: "greptile",
|
|
4608
|
+
suggestedAction: "wait",
|
|
4609
|
+
message: `Greptile evidence is stale (reviewed ${greptile.reviewedSha ?? staleSignal?.reviewedSha ?? "unknown"}, current ${evidence.headSha || "unknown"}).`,
|
|
4610
|
+
headSha: evidence.headSha || null,
|
|
4611
|
+
reviewedSha: greptile.reviewedSha ?? staleSignal?.reviewedSha ?? null
|
|
4612
|
+
});
|
|
4613
|
+
}
|
|
4614
|
+
for (const signal of pendingGreptileApiSignals) {
|
|
4615
|
+
addReason({
|
|
4616
|
+
code: "greptile_pending",
|
|
4617
|
+
reasonClass: "pending",
|
|
4618
|
+
surface: "greptile",
|
|
4619
|
+
suggestedAction: "wait",
|
|
4620
|
+
message: `Greptile API/MCP review is pending for the current PR head${signal.bodyExcerpt ? `: ${signal.bodyExcerpt}` : "."}`,
|
|
4621
|
+
headSha: evidence.headSha || null,
|
|
4622
|
+
reviewedSha: signal.reviewedSha ?? null
|
|
4623
|
+
});
|
|
4624
|
+
}
|
|
4625
|
+
for (const signal of unknownGreptileApiSignals) {
|
|
4626
|
+
addReason({
|
|
4627
|
+
code: "greptile_api_status_unknown",
|
|
4628
|
+
reasonClass: "reject",
|
|
4629
|
+
surface: "greptile",
|
|
4630
|
+
suggestedAction: "needs_attention",
|
|
4631
|
+
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}` : "."}`,
|
|
4632
|
+
headSha: evidence.headSha || null,
|
|
4633
|
+
reviewedSha: signal.reviewedSha ?? null
|
|
4634
|
+
});
|
|
4346
4635
|
}
|
|
4347
4636
|
if (!greptile.completed) {
|
|
4348
|
-
|
|
4349
|
-
|
|
4637
|
+
addReason({
|
|
4638
|
+
code: "greptile_pending",
|
|
4639
|
+
reasonClass: "pending",
|
|
4640
|
+
surface: "greptile",
|
|
4641
|
+
suggestedAction: "wait",
|
|
4642
|
+
message: "Greptile check/review has not completed for the current PR head.",
|
|
4643
|
+
headSha: evidence.headSha || null,
|
|
4644
|
+
reviewedSha: greptile.reviewedSha ?? null
|
|
4645
|
+
});
|
|
4646
|
+
}
|
|
4647
|
+
if (!greptile.fresh) {
|
|
4648
|
+
addReason({
|
|
4649
|
+
code: "greptile_not_current_head",
|
|
4650
|
+
reasonClass: awaitingFreshGreptileProof ? "pending" : "reject",
|
|
4651
|
+
surface: "greptile",
|
|
4652
|
+
suggestedAction: awaitingFreshGreptileProof ? "wait" : "ask_greptile",
|
|
4653
|
+
message: "Greptile approval is not tied to the current PR head SHA.",
|
|
4654
|
+
headSha: evidence.headSha || null,
|
|
4655
|
+
reviewedSha: greptile.reviewedSha ?? null
|
|
4656
|
+
});
|
|
4350
4657
|
}
|
|
4351
|
-
if (!greptile.fresh)
|
|
4352
|
-
reasons.push("Greptile approval is not tied to the current PR head SHA.");
|
|
4353
4658
|
if (greptile.score && !(greptile.score.scale === 5 && greptile.score.value === 5)) {
|
|
4354
|
-
|
|
4659
|
+
addReason({
|
|
4660
|
+
code: "greptile_score_not_5",
|
|
4661
|
+
reasonClass: "reject",
|
|
4662
|
+
surface: "greptile",
|
|
4663
|
+
suggestedAction: "fix",
|
|
4664
|
+
message: `Greptile score is ${greptile.score.value}/${greptile.score.scale}; strict merge requires trusted current-head 5/5.`,
|
|
4665
|
+
headSha: evidence.headSha || null,
|
|
4666
|
+
reviewedSha: greptile.reviewedSha ?? null
|
|
4667
|
+
});
|
|
4355
4668
|
}
|
|
4356
|
-
|
|
4357
|
-
|
|
4669
|
+
const hasApprovedMapping = greptile.mapping === "score-5-of-5" || greptile.mapping === "explicit-approved";
|
|
4670
|
+
if (!greptile.score && !hasApprovedMapping) {
|
|
4671
|
+
addReason({
|
|
4672
|
+
code: "greptile_score_missing",
|
|
4673
|
+
reasonClass: awaitingFreshGreptileProof ? "pending" : "reject",
|
|
4674
|
+
surface: "greptile",
|
|
4675
|
+
suggestedAction: awaitingFreshGreptileProof ? "wait" : "ask_greptile",
|
|
4676
|
+
message: "No parseable Greptile 5/5 score or direct current-head Greptile API APPROVED mapping was found from trusted evidence; merge is blocked.",
|
|
4677
|
+
headSha: evidence.headSha || null,
|
|
4678
|
+
reviewedSha: greptile.reviewedSha ?? null
|
|
4679
|
+
});
|
|
4358
4680
|
}
|
|
4359
4681
|
if (greptile.mapping === "unproven") {
|
|
4360
|
-
|
|
4682
|
+
addReason({
|
|
4683
|
+
code: "greptile_mapping_unproven",
|
|
4684
|
+
reasonClass: awaitingFreshGreptileProof ? "pending" : "reject",
|
|
4685
|
+
surface: "greptile",
|
|
4686
|
+
suggestedAction: awaitingFreshGreptileProof ? "wait" : "ask_greptile",
|
|
4687
|
+
message: "Greptile approval mapping is unproven; PR body/title or a green check alone cannot approve merge.",
|
|
4688
|
+
headSha: evidence.headSha || null,
|
|
4689
|
+
reviewedSha: greptile.reviewedSha ?? null
|
|
4690
|
+
});
|
|
4361
4691
|
}
|
|
4362
|
-
|
|
4363
|
-
|
|
4692
|
+
for (const blocker of greptile.blockers) {
|
|
4693
|
+
addReason({
|
|
4694
|
+
code: "greptile_blocker_text",
|
|
4695
|
+
reasonClass: "reject",
|
|
4696
|
+
surface: "greptile",
|
|
4697
|
+
suggestedAction: "fix",
|
|
4698
|
+
message: `Greptile/blocker text: ${blocker}`,
|
|
4699
|
+
headSha: evidence.headSha || null,
|
|
4700
|
+
reviewedSha: greptile.reviewedSha ?? null
|
|
4701
|
+
});
|
|
4702
|
+
}
|
|
4703
|
+
for (const comment of greptile.unresolvedComments) {
|
|
4704
|
+
addReason({
|
|
4705
|
+
code: "greptile_unresolved_comment",
|
|
4706
|
+
reasonClass: "reject",
|
|
4707
|
+
surface: "greptile",
|
|
4708
|
+
suggestedAction: "fix",
|
|
4709
|
+
message: comment,
|
|
4710
|
+
headSha: evidence.headSha || null,
|
|
4711
|
+
reviewedSha: greptile.reviewedSha ?? null
|
|
4712
|
+
});
|
|
4364
4713
|
}
|
|
4365
|
-
if (greptile.unresolvedComments.length > 0)
|
|
4366
|
-
reasons.push(...greptile.unresolvedComments);
|
|
4367
4714
|
if (!greptile.approved)
|
|
4368
4715
|
warnings.push(`Greptile approval mapping is ${greptile.mapping}.`);
|
|
4369
|
-
|
|
4716
|
+
const pending = reasonDetails.length > 0 && reasonDetails.every((reason) => reason.reasonClass === "pending");
|
|
4717
|
+
return { reasons: reasonDetails.map((reason) => reason.message), reasonDetails, warnings, pending };
|
|
4370
4718
|
}
|
|
4371
4719
|
function evaluateStrictPrMergeGate(evidence) {
|
|
4372
4720
|
const evaluated = evaluateEvidence(evidence);
|
|
4373
|
-
const approved = evaluated.
|
|
4721
|
+
const approved = evaluated.reasonDetails.length === 0 && evidence.greptile.approved;
|
|
4374
4722
|
return {
|
|
4375
4723
|
approved,
|
|
4376
4724
|
pending: evaluated.pending,
|
|
4377
4725
|
reasons: evaluated.reasons,
|
|
4726
|
+
reasonDetails: evaluated.reasonDetails,
|
|
4378
4727
|
warnings: evaluated.warnings,
|
|
4379
4728
|
actionableFeedback: evaluated.reasons,
|
|
4380
4729
|
evidence
|
|
@@ -5178,7 +5527,7 @@ async function runGreptileReviewForPr(options) {
|
|
|
5178
5527
|
};
|
|
5179
5528
|
}
|
|
5180
5529
|
const blockerScanBody = stripHtml(reviewBody).replace(/\b(?:no|without|zero)\s+blockers?\b/gi, " ").replace(/\bno\s+changes\s+requested\b/gi, " ");
|
|
5181
|
-
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)) {
|
|
5530
|
+
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)) {
|
|
5182
5531
|
reasons.push(`[AI Review] ${repoName}#${prNumber} summary indicates the PR is not safe to merge.`);
|
|
5183
5532
|
return {
|
|
5184
5533
|
verdict: "REJECT",
|
|
@@ -5260,6 +5609,7 @@ async function runGreptileReviewForPr(options) {
|
|
|
5260
5609
|
approved: strictGate.approved,
|
|
5261
5610
|
pending: strictGate.pending,
|
|
5262
5611
|
reasons: strictGate.reasons,
|
|
5612
|
+
reasonDetails: strictGate.reasonDetails,
|
|
5263
5613
|
warnings: strictGate.warnings,
|
|
5264
5614
|
greptile: strictGate.evidence.greptile,
|
|
5265
5615
|
readErrors: strictGate.evidence.readErrors
|
|
@@ -5282,6 +5632,7 @@ async function runGreptileReviewForPr(options) {
|
|
|
5282
5632
|
approved: strictGate.approved,
|
|
5283
5633
|
pending: strictGate.pending,
|
|
5284
5634
|
reasons: strictGate.reasons,
|
|
5635
|
+
reasonDetails: strictGate.reasonDetails,
|
|
5285
5636
|
warnings: strictGate.warnings,
|
|
5286
5637
|
greptile: strictGate.evidence.greptile,
|
|
5287
5638
|
readErrors: strictGate.evidence.readErrors
|
|
@@ -5408,6 +5759,7 @@ async function runGithubGreptileFallbackReviewForPr(options) {
|
|
|
5408
5759
|
approved: strictGate.approved,
|
|
5409
5760
|
pending: strictGate.pending,
|
|
5410
5761
|
reasons: strictGate.reasons,
|
|
5762
|
+
reasonDetails: strictGate.reasonDetails,
|
|
5411
5763
|
warnings: strictGate.warnings,
|
|
5412
5764
|
greptile: strictGate.evidence.greptile
|
|
5413
5765
|
},
|
|
@@ -5430,6 +5782,7 @@ async function runGithubGreptileFallbackReviewForPr(options) {
|
|
|
5430
5782
|
approved: strictGate.approved,
|
|
5431
5783
|
pending: strictGate.pending,
|
|
5432
5784
|
reasons: strictGate.reasons,
|
|
5785
|
+
reasonDetails: strictGate.reasonDetails,
|
|
5433
5786
|
warnings: strictGate.warnings,
|
|
5434
5787
|
greptile: strictGate.evidence.greptile
|
|
5435
5788
|
},
|
|
@@ -5545,19 +5898,25 @@ function shouldTriggerGreptileReview(existingReview, expectedHeadSha) {
|
|
|
5545
5898
|
if ((existingReview.metadata?.checkHeadSha || "") !== expectedHeadSha) {
|
|
5546
5899
|
return true;
|
|
5547
5900
|
}
|
|
5548
|
-
return
|
|
5901
|
+
return false;
|
|
5549
5902
|
}
|
|
5550
5903
|
function shouldContinueGreptileMcpPolling(options) {
|
|
5551
5904
|
if (options.githubCheckState.completed) {
|
|
5552
5905
|
return false;
|
|
5553
5906
|
}
|
|
5907
|
+
if (options.attempt + 1 >= options.pollAttempts) {
|
|
5908
|
+
return false;
|
|
5909
|
+
}
|
|
5554
5910
|
if (options.selectedReview && !isGreptileReviewTerminal(options.selectedReview.status)) {
|
|
5555
5911
|
return true;
|
|
5556
5912
|
}
|
|
5557
|
-
return
|
|
5913
|
+
return true;
|
|
5558
5914
|
}
|
|
5559
5915
|
function shouldContinueGithubGreptileFallbackPolling(options) {
|
|
5560
5916
|
const waitingForVisiblePendingReview = options.checkState.pending && (!options.fallbackReview || !options.selectedReview && !options.approvedViaReviewedAncestor);
|
|
5917
|
+
if (options.attempt + 1 >= options.pollAttempts) {
|
|
5918
|
+
return false;
|
|
5919
|
+
}
|
|
5561
5920
|
if (waitingForVisiblePendingReview) {
|
|
5562
5921
|
return true;
|
|
5563
5922
|
}
|
|
@@ -7009,7 +7368,7 @@ function gitOpenPr(options) {
|
|
|
7009
7368
|
"",
|
|
7010
7369
|
"## Review",
|
|
7011
7370
|
"- Completion verification will run validation, verifier review, and PR policy checks.",
|
|
7012
|
-
"- When repository policy allows it, Rig
|
|
7371
|
+
"- When repository policy allows it, Rig attempts an immediate strict-gated, head-locked merge after approval."
|
|
7013
7372
|
].join(`
|
|
7014
7373
|
`);
|
|
7015
7374
|
const preCheck = runCapture2(withGhRepo([gh, "pr", "list", "--state", "merged", "--head", branch, "--json", "url,mergedAt", "--jq", ".[0]"], repoNameWithOwner), repoRoot);
|