@h-rig/runtime 0.0.6-alpha.22 → 0.0.6-alpha.23
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-dispatch.js +333 -23
- package/dist/src/control-plane/agent-wrapper.js +336 -23
- package/dist/src/control-plane/harness-main.js +142 -17
- package/dist/src/control-plane/hooks/completion-verification.js +142 -17
- package/dist/src/control-plane/native/harness-cli.js +142 -17
- package/dist/src/control-plane/native/pr-automation.js +142 -17
- package/dist/src/control-plane/native/pr-review-gate.js +142 -17
- package/dist/src/control-plane/native/run-ops.js +1 -1
- package/dist/src/control-plane/native/task-ops.js +142 -17
- package/dist/src/control-plane/native/verifier.js +142 -17
- package/dist/src/control-plane/pi-sessiond/bin.js +793 -0
- package/dist/src/control-plane/pi-sessiond/client.js +41 -0
- package/dist/src/control-plane/pi-sessiond/event-hub.js +59 -0
- package/dist/src/control-plane/pi-sessiond/extension-ui-context.js +198 -0
- package/dist/src/control-plane/pi-sessiond/launcher.js +163 -0
- package/dist/src/control-plane/pi-sessiond/server.js +802 -0
- package/dist/src/control-plane/pi-sessiond/session-service.js +540 -0
- package/dist/src/control-plane/pi-sessiond/types.js +1 -0
- package/dist/src/control-plane/runtime/index.js +17 -0
- package/dist/src/control-plane/runtime/isolation/home.js +17 -0
- package/dist/src/control-plane/runtime/isolation/index.js +17 -0
- package/dist/src/control-plane/runtime/isolation/runner.js +17 -0
- package/dist/src/control-plane/runtime/isolation.js +17 -0
- package/dist/src/control-plane/runtime/queue.js +17 -0
- package/package.json +7 -7
|
@@ -3631,6 +3631,57 @@ function flattenPaginatedArray(value) {
|
|
|
3631
3631
|
}
|
|
3632
3632
|
return value;
|
|
3633
3633
|
}
|
|
3634
|
+
function parseConcatenatedJsonValues(value) {
|
|
3635
|
+
const text = value.trim();
|
|
3636
|
+
const docs = [];
|
|
3637
|
+
let start = null;
|
|
3638
|
+
let depth = 0;
|
|
3639
|
+
let inString = false;
|
|
3640
|
+
let escape = false;
|
|
3641
|
+
for (let index = 0;index < text.length; index += 1) {
|
|
3642
|
+
const char = text[index];
|
|
3643
|
+
if (start === null) {
|
|
3644
|
+
if (/\s/.test(char))
|
|
3645
|
+
continue;
|
|
3646
|
+
start = index;
|
|
3647
|
+
}
|
|
3648
|
+
if (inString) {
|
|
3649
|
+
if (escape) {
|
|
3650
|
+
escape = false;
|
|
3651
|
+
} else if (char === "\\") {
|
|
3652
|
+
escape = true;
|
|
3653
|
+
} else if (char === '"') {
|
|
3654
|
+
inString = false;
|
|
3655
|
+
}
|
|
3656
|
+
continue;
|
|
3657
|
+
}
|
|
3658
|
+
if (char === '"') {
|
|
3659
|
+
inString = true;
|
|
3660
|
+
continue;
|
|
3661
|
+
}
|
|
3662
|
+
if (char === "{" || char === "[") {
|
|
3663
|
+
depth += 1;
|
|
3664
|
+
continue;
|
|
3665
|
+
}
|
|
3666
|
+
if (char === "}" || char === "]") {
|
|
3667
|
+
depth -= 1;
|
|
3668
|
+
if (depth < 0)
|
|
3669
|
+
return { value: docs, error: "unexpected JSON close delimiter" };
|
|
3670
|
+
if (depth === 0 && start !== null) {
|
|
3671
|
+
const segment = text.slice(start, index + 1);
|
|
3672
|
+
try {
|
|
3673
|
+
docs.push(JSON.parse(segment));
|
|
3674
|
+
} catch (error) {
|
|
3675
|
+
return { value: docs, error: error instanceof Error ? error.message : String(error) };
|
|
3676
|
+
}
|
|
3677
|
+
start = null;
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
}
|
|
3681
|
+
if (inString || depth !== 0 || start !== null)
|
|
3682
|
+
return { value: docs, error: "incomplete JSON stream" };
|
|
3683
|
+
return { value: docs };
|
|
3684
|
+
}
|
|
3634
3685
|
function parseJsonArray(value) {
|
|
3635
3686
|
if (!value?.trim())
|
|
3636
3687
|
return { value: [], error: "empty JSON output" };
|
|
@@ -3639,7 +3690,11 @@ function parseJsonArray(value) {
|
|
|
3639
3690
|
const flattened = flattenPaginatedArray(parsed);
|
|
3640
3691
|
return flattened ? { value: flattened } : { value: [], error: "JSON output was not an array" };
|
|
3641
3692
|
} catch (error) {
|
|
3642
|
-
|
|
3693
|
+
const streamed = parseConcatenatedJsonValues(value);
|
|
3694
|
+
if (streamed.error)
|
|
3695
|
+
return { value: [], error: error instanceof Error ? error.message : String(error) };
|
|
3696
|
+
const flattened = streamed.value.flatMap((entry) => flattenPaginatedArray(entry) ?? []);
|
|
3697
|
+
return flattened.length > 0 || streamed.value.length === 0 ? { value: flattened } : { value: [], error: "JSON output was not an array" };
|
|
3643
3698
|
}
|
|
3644
3699
|
}
|
|
3645
3700
|
function parseGithubPrUrl(prUrl) {
|
|
@@ -3728,6 +3783,24 @@ function isStrictFiveOfFive(score) {
|
|
|
3728
3783
|
function containsConflictingScoreText(input) {
|
|
3729
3784
|
return parseGreptileScores(input).some((score) => !isStrictFiveOfFive(score));
|
|
3730
3785
|
}
|
|
3786
|
+
function extractGreptileCommentBlock(input) {
|
|
3787
|
+
const match = input.match(/<!--\s*greptile_comment\s*-->([\s\S]*?)<!--\s*\/greptile_comment\s*-->/i);
|
|
3788
|
+
return match?.[1]?.trim() ?? null;
|
|
3789
|
+
}
|
|
3790
|
+
function extractGreptileBodyReviewedSha(input) {
|
|
3791
|
+
const block = extractGreptileCommentBlock(input);
|
|
3792
|
+
if (!block)
|
|
3793
|
+
return null;
|
|
3794
|
+
const commitLink = block.match(/\/commit\/([0-9a-f]{40})(?:\b|[^0-9a-f])/i);
|
|
3795
|
+
return commitLink?.[1]?.toLowerCase() ?? null;
|
|
3796
|
+
}
|
|
3797
|
+
function isoAtOrAfter(value, floor) {
|
|
3798
|
+
if (!value || !floor)
|
|
3799
|
+
return false;
|
|
3800
|
+
const valueMs = Date.parse(value);
|
|
3801
|
+
const floorMs = Date.parse(floor);
|
|
3802
|
+
return Number.isFinite(valueMs) && Number.isFinite(floorMs) && valueMs >= floorMs;
|
|
3803
|
+
}
|
|
3731
3804
|
function greptileStatusVerdict(status) {
|
|
3732
3805
|
const normalized = String(status ?? "").trim().toUpperCase().replace(/[\s-]+/g, "_");
|
|
3733
3806
|
if (!normalized)
|
|
@@ -4102,9 +4175,18 @@ function commentAuthorLogin(comment) {
|
|
|
4102
4175
|
}
|
|
4103
4176
|
function collectGreptileSignals(evidence) {
|
|
4104
4177
|
const signals = [];
|
|
4178
|
+
const greptileBodyReviewedSha = extractGreptileBodyReviewedSha(evidence.body);
|
|
4179
|
+
const trustedGreptileBody = Boolean(greptileBodyReviewedSha && isGreptileGithubLogin(evidence.bodyEditorLogin) && isoAtOrAfter(evidence.bodyLastEditedAt, evidence.headCommittedDate));
|
|
4105
4180
|
const contextSources = [
|
|
4106
4181
|
{ source: "pr-title", body: evidence.title ?? "" },
|
|
4107
|
-
{
|
|
4182
|
+
{
|
|
4183
|
+
source: "pr-body",
|
|
4184
|
+
body: evidence.body,
|
|
4185
|
+
trusted: trustedGreptileBody,
|
|
4186
|
+
authorLogin: trustedGreptileBody ? evidence.bodyEditorLogin ?? null : null,
|
|
4187
|
+
reviewedSha: greptileBodyReviewedSha,
|
|
4188
|
+
verdict: trustedGreptileBody ? "completed" : null
|
|
4189
|
+
}
|
|
4108
4190
|
];
|
|
4109
4191
|
for (const context of contextSources) {
|
|
4110
4192
|
if (!context.body.trim())
|
|
@@ -4116,7 +4198,10 @@ function collectGreptileSignals(evidence) {
|
|
|
4116
4198
|
source: context.source,
|
|
4117
4199
|
body: context.body,
|
|
4118
4200
|
currentHeadSha: evidence.currentHeadSha,
|
|
4119
|
-
trusted:
|
|
4201
|
+
trusted: context.trusted === true,
|
|
4202
|
+
authorLogin: context.authorLogin,
|
|
4203
|
+
reviewedSha: context.reviewedSha,
|
|
4204
|
+
verdict: context.verdict,
|
|
4120
4205
|
blocker: contextBlocker,
|
|
4121
4206
|
actionable: contextBlocker
|
|
4122
4207
|
}));
|
|
@@ -4312,7 +4397,7 @@ function deriveGreptileEvidence(input) {
|
|
|
4312
4397
|
const hasGreptileEvidence = trustedSignals.length > 0 || signals.some((signal) => /greptile/i.test(signal.body));
|
|
4313
4398
|
const approved = fresh && completed && !blockers.length && !unresolvedComments.length && currentPendingApiSignals.length === 0 && (approvedByScore || approvedByExplicitMapping);
|
|
4314
4399
|
const mapping = !hasGreptileEvidence ? "missing" : staleSignals.length > 0 && !approvingSignal ? "stale" : approvedByScore ? "score-5-of-5" : approvedByExplicitMapping ? "explicit-approved" : "unproven";
|
|
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";
|
|
4400
|
+
const source = approvingSignal?.source === "api" ? "api" : approvingSignal?.source === "github-review" ? "github-review" : approvingSignal?.source === "pr-body" || approvingSignal?.source === "pr-title" ? "pr-body" : 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";
|
|
4316
4401
|
return {
|
|
4317
4402
|
source,
|
|
4318
4403
|
currentHeadSha: input.currentHeadSha,
|
|
@@ -4333,17 +4418,48 @@ function isGreptileCheckDetail(check) {
|
|
|
4333
4418
|
return isGreptileLabel(checkName(check)) || isGreptileGithubLogin(check.app?.slug) || isGreptileGithubLogin(check.app?.owner?.login) || isGreptileLabel(check.app?.name);
|
|
4334
4419
|
}
|
|
4335
4420
|
async function collectGreptileCheckDetails(input) {
|
|
4336
|
-
const checkRunsRead = await
|
|
4421
|
+
const checkRunsRead = await runJsonObject(input.command, [
|
|
4337
4422
|
"api",
|
|
4338
4423
|
`repos/${input.repoName}/commits/${input.headSha}/check-runs`,
|
|
4339
|
-
"
|
|
4340
|
-
"
|
|
4341
|
-
"--jq",
|
|
4342
|
-
"map(.check_runs // []) | add // []"
|
|
4424
|
+
"-F",
|
|
4425
|
+
"per_page=100"
|
|
4343
4426
|
], input.projectRoot);
|
|
4344
|
-
const checkRuns = checkRunsRead.value.map(normalizeStatusCheck).filter((entry) => !!entry).filter(isGreptileCheckDetail);
|
|
4427
|
+
const checkRuns = arrayField(checkRunsRead.value, "check_runs").map(normalizeStatusCheck).filter((entry) => !!entry).filter(isGreptileCheckDetail);
|
|
4345
4428
|
return checkRunsRead.error ? { value: checkRuns, error: checkRunsRead.error } : { value: checkRuns };
|
|
4346
4429
|
}
|
|
4430
|
+
async function collectPullRequestProvenance(input) {
|
|
4431
|
+
const response = await runJsonObject(input.command, [
|
|
4432
|
+
"api",
|
|
4433
|
+
"graphql",
|
|
4434
|
+
"-F",
|
|
4435
|
+
`owner=${input.owner}`,
|
|
4436
|
+
"-F",
|
|
4437
|
+
`name=${input.name}`,
|
|
4438
|
+
"-F",
|
|
4439
|
+
`prNumber=${input.prNumber}`,
|
|
4440
|
+
"-f",
|
|
4441
|
+
"query=query($owner: String!, $name: String!, $prNumber: Int!) { repository(owner:$owner, name:$name) { pullRequest(number:$prNumber) { lastEditedAt editor { login } commits(last: 1) { nodes { commit { oid committedDate } } } } } }"
|
|
4442
|
+
], input.projectRoot);
|
|
4443
|
+
if (response.error)
|
|
4444
|
+
return { value: {}, error: response.error };
|
|
4445
|
+
const data = response.value.data;
|
|
4446
|
+
const repository = data?.repository;
|
|
4447
|
+
const pullRequest = repository?.pullRequest;
|
|
4448
|
+
if (!pullRequest)
|
|
4449
|
+
return { value: {}, error: "GitHub pullRequest provenance response did not include a pullRequest object" };
|
|
4450
|
+
const editor = pullRequest.editor;
|
|
4451
|
+
const commits = pullRequest.commits;
|
|
4452
|
+
const nodes = Array.isArray(commits?.nodes) ? commits.nodes : [];
|
|
4453
|
+
const latestCommitNode = nodes[nodes.length - 1];
|
|
4454
|
+
const latestCommit = latestCommitNode?.commit;
|
|
4455
|
+
return {
|
|
4456
|
+
value: {
|
|
4457
|
+
bodyEditorLogin: typeof editor?.login === "string" ? editor.login : null,
|
|
4458
|
+
bodyLastEditedAt: typeof pullRequest.lastEditedAt === "string" ? pullRequest.lastEditedAt : null,
|
|
4459
|
+
headCommittedDate: typeof latestCommit?.committedDate === "string" ? latestCommit.committedDate : null
|
|
4460
|
+
}
|
|
4461
|
+
};
|
|
4462
|
+
}
|
|
4347
4463
|
async function collectReviewThreads(input) {
|
|
4348
4464
|
const reviewThreads = [];
|
|
4349
4465
|
let afterCursor = null;
|
|
@@ -4421,11 +4537,19 @@ async function collectPrReviewEvidence(input) {
|
|
|
4421
4537
|
const baseRefName = firstString(view, ["baseRefName"]);
|
|
4422
4538
|
const statusCheckRollup = arrayField(view, "statusCheckRollup").map(normalizeStatusCheck).filter((entry) => !!entry);
|
|
4423
4539
|
const reviews = arrayField(view, "reviews").map(normalizeReview).filter((entry) => !!entry);
|
|
4424
|
-
const
|
|
4540
|
+
const provenanceRead = await collectPullRequestProvenance({
|
|
4541
|
+
command: input.command,
|
|
4542
|
+
projectRoot: input.projectRoot,
|
|
4543
|
+
owner: parsed.owner,
|
|
4544
|
+
name: parsed.repo,
|
|
4545
|
+
prNumber: parsed.prNumber
|
|
4546
|
+
});
|
|
4547
|
+
const provenance = provenanceRead.value;
|
|
4548
|
+
const reviewCommentsRead = await runJsonArray(input.command, ["api", `repos/${parsed.repoName}/pulls/${parsed.prNumber}/comments`, "--paginate"], input.projectRoot);
|
|
4425
4549
|
if (reviewCommentsRead.error)
|
|
4426
4550
|
readErrors.push(reviewCommentsRead.error);
|
|
4427
4551
|
const reviewComments = reviewCommentsRead.value.map(normalizeReviewComment).filter((entry) => !!entry);
|
|
4428
|
-
const issueCommentsRead = await runJsonArray(input.command, ["api", `repos/${parsed.repoName}/issues/${parsed.prNumber}/comments`, "--paginate"
|
|
4552
|
+
const issueCommentsRead = await runJsonArray(input.command, ["api", `repos/${parsed.repoName}/issues/${parsed.prNumber}/comments`, "--paginate"], input.projectRoot);
|
|
4429
4553
|
if (issueCommentsRead.error)
|
|
4430
4554
|
readErrors.push(issueCommentsRead.error);
|
|
4431
4555
|
const issueComments = issueCommentsRead.value.map(normalizeIssueComment).filter((entry) => !!entry).filter(relevantIssueComment);
|
|
@@ -4448,12 +4572,7 @@ async function collectPrReviewEvidence(input) {
|
|
|
4448
4572
|
repoName: parsed.repoName,
|
|
4449
4573
|
headSha
|
|
4450
4574
|
});
|
|
4451
|
-
if (checkDetailsRead.error)
|
|
4452
|
-
readErrors.push(checkDetailsRead.error);
|
|
4453
4575
|
greptileCheckDetails = checkDetailsRead.value;
|
|
4454
|
-
if (!checkDetailsRead.error && greptileCheckDetails.length === 0) {
|
|
4455
|
-
readErrors.push("Greptile check details could not be found for the current PR head");
|
|
4456
|
-
}
|
|
4457
4576
|
}
|
|
4458
4577
|
const checksWithGreptileDetails = [...statusCheckRollup, ...greptileCheckDetails];
|
|
4459
4578
|
const shouldCollectConfiguredGreptileApi = input.greptileApi?.enabled !== false;
|
|
@@ -4472,6 +4591,9 @@ async function collectPrReviewEvidence(input) {
|
|
|
4472
4591
|
const evidenceBase = {
|
|
4473
4592
|
title: firstString(view, ["title"]),
|
|
4474
4593
|
body: firstString(view, ["body"]),
|
|
4594
|
+
bodyEditorLogin: provenance.bodyEditorLogin ?? null,
|
|
4595
|
+
bodyLastEditedAt: provenance.bodyLastEditedAt ?? null,
|
|
4596
|
+
headCommittedDate: provenance.headCommittedDate ?? null,
|
|
4475
4597
|
reviews,
|
|
4476
4598
|
changedFileReviewComments: reviewComments,
|
|
4477
4599
|
relevantIssueComments: issueComments,
|
|
@@ -4487,6 +4609,9 @@ async function collectPrReviewEvidence(input) {
|
|
|
4487
4609
|
repoName: parsed.repoName,
|
|
4488
4610
|
title: evidenceBase.title,
|
|
4489
4611
|
body: evidenceBase.body,
|
|
4612
|
+
bodyEditorLogin: evidenceBase.bodyEditorLogin,
|
|
4613
|
+
bodyLastEditedAt: evidenceBase.bodyLastEditedAt,
|
|
4614
|
+
headCommittedDate: evidenceBase.headCommittedDate,
|
|
4490
4615
|
headSha,
|
|
4491
4616
|
headRefName: firstString(view, ["headRefName"]),
|
|
4492
4617
|
baseRefName,
|
|
@@ -4263,6 +4263,57 @@ function flattenPaginatedArray(value) {
|
|
|
4263
4263
|
}
|
|
4264
4264
|
return value;
|
|
4265
4265
|
}
|
|
4266
|
+
function parseConcatenatedJsonValues(value) {
|
|
4267
|
+
const text = value.trim();
|
|
4268
|
+
const docs = [];
|
|
4269
|
+
let start = null;
|
|
4270
|
+
let depth = 0;
|
|
4271
|
+
let inString = false;
|
|
4272
|
+
let escape = false;
|
|
4273
|
+
for (let index = 0;index < text.length; index += 1) {
|
|
4274
|
+
const char = text[index];
|
|
4275
|
+
if (start === null) {
|
|
4276
|
+
if (/\s/.test(char))
|
|
4277
|
+
continue;
|
|
4278
|
+
start = index;
|
|
4279
|
+
}
|
|
4280
|
+
if (inString) {
|
|
4281
|
+
if (escape) {
|
|
4282
|
+
escape = false;
|
|
4283
|
+
} else if (char === "\\") {
|
|
4284
|
+
escape = true;
|
|
4285
|
+
} else if (char === '"') {
|
|
4286
|
+
inString = false;
|
|
4287
|
+
}
|
|
4288
|
+
continue;
|
|
4289
|
+
}
|
|
4290
|
+
if (char === '"') {
|
|
4291
|
+
inString = true;
|
|
4292
|
+
continue;
|
|
4293
|
+
}
|
|
4294
|
+
if (char === "{" || char === "[") {
|
|
4295
|
+
depth += 1;
|
|
4296
|
+
continue;
|
|
4297
|
+
}
|
|
4298
|
+
if (char === "}" || char === "]") {
|
|
4299
|
+
depth -= 1;
|
|
4300
|
+
if (depth < 0)
|
|
4301
|
+
return { value: docs, error: "unexpected JSON close delimiter" };
|
|
4302
|
+
if (depth === 0 && start !== null) {
|
|
4303
|
+
const segment = text.slice(start, index + 1);
|
|
4304
|
+
try {
|
|
4305
|
+
docs.push(JSON.parse(segment));
|
|
4306
|
+
} catch (error) {
|
|
4307
|
+
return { value: docs, error: error instanceof Error ? error.message : String(error) };
|
|
4308
|
+
}
|
|
4309
|
+
start = null;
|
|
4310
|
+
}
|
|
4311
|
+
}
|
|
4312
|
+
}
|
|
4313
|
+
if (inString || depth !== 0 || start !== null)
|
|
4314
|
+
return { value: docs, error: "incomplete JSON stream" };
|
|
4315
|
+
return { value: docs };
|
|
4316
|
+
}
|
|
4266
4317
|
function parseJsonArray(value) {
|
|
4267
4318
|
if (!value?.trim())
|
|
4268
4319
|
return { value: [], error: "empty JSON output" };
|
|
@@ -4271,7 +4322,11 @@ function parseJsonArray(value) {
|
|
|
4271
4322
|
const flattened = flattenPaginatedArray(parsed);
|
|
4272
4323
|
return flattened ? { value: flattened } : { value: [], error: "JSON output was not an array" };
|
|
4273
4324
|
} catch (error) {
|
|
4274
|
-
|
|
4325
|
+
const streamed = parseConcatenatedJsonValues(value);
|
|
4326
|
+
if (streamed.error)
|
|
4327
|
+
return { value: [], error: error instanceof Error ? error.message : String(error) };
|
|
4328
|
+
const flattened = streamed.value.flatMap((entry) => flattenPaginatedArray(entry) ?? []);
|
|
4329
|
+
return flattened.length > 0 || streamed.value.length === 0 ? { value: flattened } : { value: [], error: "JSON output was not an array" };
|
|
4275
4330
|
}
|
|
4276
4331
|
}
|
|
4277
4332
|
function parseGithubPrUrl(prUrl) {
|
|
@@ -4360,6 +4415,24 @@ function isStrictFiveOfFive(score) {
|
|
|
4360
4415
|
function containsConflictingScoreText(input) {
|
|
4361
4416
|
return parseGreptileScores(input).some((score) => !isStrictFiveOfFive(score));
|
|
4362
4417
|
}
|
|
4418
|
+
function extractGreptileCommentBlock(input) {
|
|
4419
|
+
const match = input.match(/<!--\s*greptile_comment\s*-->([\s\S]*?)<!--\s*\/greptile_comment\s*-->/i);
|
|
4420
|
+
return match?.[1]?.trim() ?? null;
|
|
4421
|
+
}
|
|
4422
|
+
function extractGreptileBodyReviewedSha(input) {
|
|
4423
|
+
const block = extractGreptileCommentBlock(input);
|
|
4424
|
+
if (!block)
|
|
4425
|
+
return null;
|
|
4426
|
+
const commitLink = block.match(/\/commit\/([0-9a-f]{40})(?:\b|[^0-9a-f])/i);
|
|
4427
|
+
return commitLink?.[1]?.toLowerCase() ?? null;
|
|
4428
|
+
}
|
|
4429
|
+
function isoAtOrAfter(value, floor) {
|
|
4430
|
+
if (!value || !floor)
|
|
4431
|
+
return false;
|
|
4432
|
+
const valueMs = Date.parse(value);
|
|
4433
|
+
const floorMs = Date.parse(floor);
|
|
4434
|
+
return Number.isFinite(valueMs) && Number.isFinite(floorMs) && valueMs >= floorMs;
|
|
4435
|
+
}
|
|
4363
4436
|
function greptileStatusVerdict(status) {
|
|
4364
4437
|
const normalized = String(status ?? "").trim().toUpperCase().replace(/[\s-]+/g, "_");
|
|
4365
4438
|
if (!normalized)
|
|
@@ -4734,9 +4807,18 @@ function commentAuthorLogin(comment) {
|
|
|
4734
4807
|
}
|
|
4735
4808
|
function collectGreptileSignals(evidence) {
|
|
4736
4809
|
const signals = [];
|
|
4810
|
+
const greptileBodyReviewedSha = extractGreptileBodyReviewedSha(evidence.body);
|
|
4811
|
+
const trustedGreptileBody = Boolean(greptileBodyReviewedSha && isGreptileGithubLogin(evidence.bodyEditorLogin) && isoAtOrAfter(evidence.bodyLastEditedAt, evidence.headCommittedDate));
|
|
4737
4812
|
const contextSources = [
|
|
4738
4813
|
{ source: "pr-title", body: evidence.title ?? "" },
|
|
4739
|
-
{
|
|
4814
|
+
{
|
|
4815
|
+
source: "pr-body",
|
|
4816
|
+
body: evidence.body,
|
|
4817
|
+
trusted: trustedGreptileBody,
|
|
4818
|
+
authorLogin: trustedGreptileBody ? evidence.bodyEditorLogin ?? null : null,
|
|
4819
|
+
reviewedSha: greptileBodyReviewedSha,
|
|
4820
|
+
verdict: trustedGreptileBody ? "completed" : null
|
|
4821
|
+
}
|
|
4740
4822
|
];
|
|
4741
4823
|
for (const context of contextSources) {
|
|
4742
4824
|
if (!context.body.trim())
|
|
@@ -4748,7 +4830,10 @@ function collectGreptileSignals(evidence) {
|
|
|
4748
4830
|
source: context.source,
|
|
4749
4831
|
body: context.body,
|
|
4750
4832
|
currentHeadSha: evidence.currentHeadSha,
|
|
4751
|
-
trusted:
|
|
4833
|
+
trusted: context.trusted === true,
|
|
4834
|
+
authorLogin: context.authorLogin,
|
|
4835
|
+
reviewedSha: context.reviewedSha,
|
|
4836
|
+
verdict: context.verdict,
|
|
4752
4837
|
blocker: contextBlocker,
|
|
4753
4838
|
actionable: contextBlocker
|
|
4754
4839
|
}));
|
|
@@ -4944,7 +5029,7 @@ function deriveGreptileEvidence(input) {
|
|
|
4944
5029
|
const hasGreptileEvidence = trustedSignals.length > 0 || signals.some((signal) => /greptile/i.test(signal.body));
|
|
4945
5030
|
const approved = fresh && completed && !blockers.length && !unresolvedComments.length && currentPendingApiSignals.length === 0 && (approvedByScore || approvedByExplicitMapping);
|
|
4946
5031
|
const mapping = !hasGreptileEvidence ? "missing" : staleSignals.length > 0 && !approvingSignal ? "stale" : approvedByScore ? "score-5-of-5" : approvedByExplicitMapping ? "explicit-approved" : "unproven";
|
|
4947
|
-
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";
|
|
5032
|
+
const source = approvingSignal?.source === "api" ? "api" : approvingSignal?.source === "github-review" ? "github-review" : approvingSignal?.source === "pr-body" || approvingSignal?.source === "pr-title" ? "pr-body" : 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";
|
|
4948
5033
|
return {
|
|
4949
5034
|
source,
|
|
4950
5035
|
currentHeadSha: input.currentHeadSha,
|
|
@@ -4965,17 +5050,48 @@ function isGreptileCheckDetail(check) {
|
|
|
4965
5050
|
return isGreptileLabel(checkName(check)) || isGreptileGithubLogin(check.app?.slug) || isGreptileGithubLogin(check.app?.owner?.login) || isGreptileLabel(check.app?.name);
|
|
4966
5051
|
}
|
|
4967
5052
|
async function collectGreptileCheckDetails(input) {
|
|
4968
|
-
const checkRunsRead = await
|
|
5053
|
+
const checkRunsRead = await runJsonObject(input.command, [
|
|
4969
5054
|
"api",
|
|
4970
5055
|
`repos/${input.repoName}/commits/${input.headSha}/check-runs`,
|
|
4971
|
-
"
|
|
4972
|
-
"
|
|
4973
|
-
"--jq",
|
|
4974
|
-
"map(.check_runs // []) | add // []"
|
|
5056
|
+
"-F",
|
|
5057
|
+
"per_page=100"
|
|
4975
5058
|
], input.projectRoot);
|
|
4976
|
-
const checkRuns = checkRunsRead.value.map(normalizeStatusCheck).filter((entry) => !!entry).filter(isGreptileCheckDetail);
|
|
5059
|
+
const checkRuns = arrayField(checkRunsRead.value, "check_runs").map(normalizeStatusCheck).filter((entry) => !!entry).filter(isGreptileCheckDetail);
|
|
4977
5060
|
return checkRunsRead.error ? { value: checkRuns, error: checkRunsRead.error } : { value: checkRuns };
|
|
4978
5061
|
}
|
|
5062
|
+
async function collectPullRequestProvenance(input) {
|
|
5063
|
+
const response = await runJsonObject(input.command, [
|
|
5064
|
+
"api",
|
|
5065
|
+
"graphql",
|
|
5066
|
+
"-F",
|
|
5067
|
+
`owner=${input.owner}`,
|
|
5068
|
+
"-F",
|
|
5069
|
+
`name=${input.name}`,
|
|
5070
|
+
"-F",
|
|
5071
|
+
`prNumber=${input.prNumber}`,
|
|
5072
|
+
"-f",
|
|
5073
|
+
"query=query($owner: String!, $name: String!, $prNumber: Int!) { repository(owner:$owner, name:$name) { pullRequest(number:$prNumber) { lastEditedAt editor { login } commits(last: 1) { nodes { commit { oid committedDate } } } } } }"
|
|
5074
|
+
], input.projectRoot);
|
|
5075
|
+
if (response.error)
|
|
5076
|
+
return { value: {}, error: response.error };
|
|
5077
|
+
const data = response.value.data;
|
|
5078
|
+
const repository = data?.repository;
|
|
5079
|
+
const pullRequest = repository?.pullRequest;
|
|
5080
|
+
if (!pullRequest)
|
|
5081
|
+
return { value: {}, error: "GitHub pullRequest provenance response did not include a pullRequest object" };
|
|
5082
|
+
const editor = pullRequest.editor;
|
|
5083
|
+
const commits = pullRequest.commits;
|
|
5084
|
+
const nodes = Array.isArray(commits?.nodes) ? commits.nodes : [];
|
|
5085
|
+
const latestCommitNode = nodes[nodes.length - 1];
|
|
5086
|
+
const latestCommit = latestCommitNode?.commit;
|
|
5087
|
+
return {
|
|
5088
|
+
value: {
|
|
5089
|
+
bodyEditorLogin: typeof editor?.login === "string" ? editor.login : null,
|
|
5090
|
+
bodyLastEditedAt: typeof pullRequest.lastEditedAt === "string" ? pullRequest.lastEditedAt : null,
|
|
5091
|
+
headCommittedDate: typeof latestCommit?.committedDate === "string" ? latestCommit.committedDate : null
|
|
5092
|
+
}
|
|
5093
|
+
};
|
|
5094
|
+
}
|
|
4979
5095
|
async function collectReviewThreads(input) {
|
|
4980
5096
|
const reviewThreads = [];
|
|
4981
5097
|
let afterCursor = null;
|
|
@@ -5053,11 +5169,19 @@ async function collectPrReviewEvidence(input) {
|
|
|
5053
5169
|
const baseRefName = firstString(view, ["baseRefName"]);
|
|
5054
5170
|
const statusCheckRollup = arrayField(view, "statusCheckRollup").map(normalizeStatusCheck).filter((entry) => !!entry);
|
|
5055
5171
|
const reviews = arrayField(view, "reviews").map(normalizeReview).filter((entry) => !!entry);
|
|
5056
|
-
const
|
|
5172
|
+
const provenanceRead = await collectPullRequestProvenance({
|
|
5173
|
+
command: input.command,
|
|
5174
|
+
projectRoot: input.projectRoot,
|
|
5175
|
+
owner: parsed.owner,
|
|
5176
|
+
name: parsed.repo,
|
|
5177
|
+
prNumber: parsed.prNumber
|
|
5178
|
+
});
|
|
5179
|
+
const provenance = provenanceRead.value;
|
|
5180
|
+
const reviewCommentsRead = await runJsonArray(input.command, ["api", `repos/${parsed.repoName}/pulls/${parsed.prNumber}/comments`, "--paginate"], input.projectRoot);
|
|
5057
5181
|
if (reviewCommentsRead.error)
|
|
5058
5182
|
readErrors.push(reviewCommentsRead.error);
|
|
5059
5183
|
const reviewComments = reviewCommentsRead.value.map(normalizeReviewComment).filter((entry) => !!entry);
|
|
5060
|
-
const issueCommentsRead = await runJsonArray(input.command, ["api", `repos/${parsed.repoName}/issues/${parsed.prNumber}/comments`, "--paginate"
|
|
5184
|
+
const issueCommentsRead = await runJsonArray(input.command, ["api", `repos/${parsed.repoName}/issues/${parsed.prNumber}/comments`, "--paginate"], input.projectRoot);
|
|
5061
5185
|
if (issueCommentsRead.error)
|
|
5062
5186
|
readErrors.push(issueCommentsRead.error);
|
|
5063
5187
|
const issueComments = issueCommentsRead.value.map(normalizeIssueComment).filter((entry) => !!entry).filter(relevantIssueComment);
|
|
@@ -5080,12 +5204,7 @@ async function collectPrReviewEvidence(input) {
|
|
|
5080
5204
|
repoName: parsed.repoName,
|
|
5081
5205
|
headSha
|
|
5082
5206
|
});
|
|
5083
|
-
if (checkDetailsRead.error)
|
|
5084
|
-
readErrors.push(checkDetailsRead.error);
|
|
5085
5207
|
greptileCheckDetails = checkDetailsRead.value;
|
|
5086
|
-
if (!checkDetailsRead.error && greptileCheckDetails.length === 0) {
|
|
5087
|
-
readErrors.push("Greptile check details could not be found for the current PR head");
|
|
5088
|
-
}
|
|
5089
5208
|
}
|
|
5090
5209
|
const checksWithGreptileDetails = [...statusCheckRollup, ...greptileCheckDetails];
|
|
5091
5210
|
const shouldCollectConfiguredGreptileApi = input.greptileApi?.enabled !== false;
|
|
@@ -5104,6 +5223,9 @@ async function collectPrReviewEvidence(input) {
|
|
|
5104
5223
|
const evidenceBase = {
|
|
5105
5224
|
title: firstString(view, ["title"]),
|
|
5106
5225
|
body: firstString(view, ["body"]),
|
|
5226
|
+
bodyEditorLogin: provenance.bodyEditorLogin ?? null,
|
|
5227
|
+
bodyLastEditedAt: provenance.bodyLastEditedAt ?? null,
|
|
5228
|
+
headCommittedDate: provenance.headCommittedDate ?? null,
|
|
5107
5229
|
reviews,
|
|
5108
5230
|
changedFileReviewComments: reviewComments,
|
|
5109
5231
|
relevantIssueComments: issueComments,
|
|
@@ -5119,6 +5241,9 @@ async function collectPrReviewEvidence(input) {
|
|
|
5119
5241
|
repoName: parsed.repoName,
|
|
5120
5242
|
title: evidenceBase.title,
|
|
5121
5243
|
body: evidenceBase.body,
|
|
5244
|
+
bodyEditorLogin: evidenceBase.bodyEditorLogin,
|
|
5245
|
+
bodyLastEditedAt: evidenceBase.bodyLastEditedAt,
|
|
5246
|
+
headCommittedDate: evidenceBase.headCommittedDate,
|
|
5122
5247
|
headSha,
|
|
5123
5248
|
headRefName: firstString(view, ["headRefName"]),
|
|
5124
5249
|
baseRefName,
|