prreviewbuddy 0.53.1 → 0.55.1
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/CHANGELOG.md +61 -0
- package/dist/{delete_review-DMJXD3bf.js → delete_review-B2bqhgtx.js} +383 -34
- package/dist/main.js +1 -1
- package/dist/server.js +211 -10
- package/package.json +1 -1
- package/static/chunk-BeqgAIrQ.js +4 -4
- package/static/chunk-BpiqRF0Y.js +1 -0
- package/static/chunk-RriGSD6H.js +1 -0
- package/static/notifications.js +1 -1
- package/static/reviews.js +1 -1
- package/static/shell.css +2 -0
- package/static/sidebar.css +137 -0
- package/static/workspace.js +36 -36
- package/static/chunk-CofqJ-Qn.js +0 -1
|
@@ -1643,7 +1643,8 @@ function completeReviewSession(session, result, context, options = {}) {
|
|
|
1643
1643
|
...session.metadata,
|
|
1644
1644
|
...context ? reviewFacts(result, context) : {
|
|
1645
1645
|
fileCoverage: void 0,
|
|
1646
|
-
discussionStats: void 0
|
|
1646
|
+
discussionStats: void 0,
|
|
1647
|
+
authorDescription: void 0
|
|
1647
1648
|
},
|
|
1648
1649
|
commitSha: options.commitSha ?? session.metadata.commitSha,
|
|
1649
1650
|
durationMs: Math.max(0, now - session.metadata.generatedAt)
|
|
@@ -1664,7 +1665,8 @@ function reviewFacts(result, context) {
|
|
|
1664
1665
|
reviewedFiles: sectionedFiles(result, context),
|
|
1665
1666
|
totalChangedFiles: context.fileCoverage.totalChangedFiles
|
|
1666
1667
|
},
|
|
1667
|
-
...context.prContext ? { discussionStats: countDiscussion(context) } : {}
|
|
1668
|
+
...context.prContext ? { discussionStats: countDiscussion(context) } : {},
|
|
1669
|
+
authorDescription: context.prContext?.pullRequest.body
|
|
1668
1670
|
};
|
|
1669
1671
|
}
|
|
1670
1672
|
/**
|
|
@@ -2237,6 +2239,7 @@ async function buildChangeSet(options) {
|
|
|
2237
2239
|
number: 0,
|
|
2238
2240
|
title: changeTitle(subjects, headRef),
|
|
2239
2241
|
body: subjects.length > 1 ? subjects.map((s) => `- ${s}`).join("\n") : null,
|
|
2242
|
+
bodySource: "commit-subjects",
|
|
2240
2243
|
base: {
|
|
2241
2244
|
ref: base.display,
|
|
2242
2245
|
sha: mergeBase
|
|
@@ -6296,6 +6299,7 @@ function sanitisePullRequest(value) {
|
|
|
6296
6299
|
};
|
|
6297
6300
|
const title = typeof value.title === "string" ? value.title.trim() : "";
|
|
6298
6301
|
if (title) pr.title = title.slice(0, 200);
|
|
6302
|
+
if (typeof value.body === "string" && value.body.trim()) pr.body = value.body;
|
|
6299
6303
|
const headSha = label(value.headSha);
|
|
6300
6304
|
if (headSha) pr.headSha = headSha;
|
|
6301
6305
|
const headRef = label(value.headRef);
|
|
@@ -6851,7 +6855,7 @@ function slug(repo) {
|
|
|
6851
6855
|
* `baseRefOid` is a diff taken against the wrong *commit*, which is worse because the branch name
|
|
6852
6856
|
* is right and everything reads as normal. A list nobody asserts is a list somebody tidies.
|
|
6853
6857
|
*/
|
|
6854
|
-
var PR_FIELDS = "number,url,title,headRefName,headRefOid,baseRefName,baseRefOid,changedFiles,author";
|
|
6858
|
+
var PR_FIELDS = "number,url,title,body,headRefName,headRefOid,baseRefName,baseRefOid,changedFiles,author";
|
|
6855
6859
|
async function viewRequest(repo, reference, cwd, env) {
|
|
6856
6860
|
return await ghJson([
|
|
6857
6861
|
"pr",
|
|
@@ -6863,7 +6867,7 @@ async function viewRequest(repo, reference, cwd, env) {
|
|
|
6863
6867
|
PR_FIELDS
|
|
6864
6868
|
], cwd, env);
|
|
6865
6869
|
}
|
|
6866
|
-
function text(value) {
|
|
6870
|
+
function text$1(value) {
|
|
6867
6871
|
return typeof value === "string" ? value.trim() : "";
|
|
6868
6872
|
}
|
|
6869
6873
|
/**
|
|
@@ -6884,30 +6888,31 @@ function fileUrlTemplate(prUrl) {
|
|
|
6884
6888
|
* email address all live in `pr_context.ts` and in nothing else.
|
|
6885
6889
|
*/
|
|
6886
6890
|
function identity(raw) {
|
|
6887
|
-
const url = text(raw.url);
|
|
6891
|
+
const url = text$1(raw.url);
|
|
6888
6892
|
const author = raw.author ?? null;
|
|
6889
6893
|
return {
|
|
6890
6894
|
provider: GITHUB_FORGE_ID,
|
|
6891
6895
|
number: typeof raw.number === "number" ? raw.number : 0,
|
|
6892
6896
|
url,
|
|
6893
|
-
title: text(raw.title),
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
+
title: text$1(raw.title),
|
|
6898
|
+
body: raw.body,
|
|
6899
|
+
headSha: text$1(raw.headRefOid),
|
|
6900
|
+
headRef: text$1(raw.headRefName),
|
|
6901
|
+
baseRef: text$1(raw.baseRefName),
|
|
6897
6902
|
filesUrl: url ? `${url}/files` : "",
|
|
6898
6903
|
fileUrlTemplate: fileUrlTemplate(url),
|
|
6899
6904
|
author: author ? {
|
|
6900
|
-
name: text(author.name),
|
|
6901
|
-
handle: text(author.login)
|
|
6905
|
+
name: text$1(author.name),
|
|
6906
|
+
handle: text$1(author.login)
|
|
6902
6907
|
} : null
|
|
6903
6908
|
};
|
|
6904
6909
|
}
|
|
6905
6910
|
/**
|
|
6906
6911
|
* The watcher's query: where the branch is, and whether the request is still open.
|
|
6907
6912
|
*
|
|
6908
|
-
* Deliberately not `PR_FIELDS`. That list exists because a review needs all
|
|
6913
|
+
* Deliberately not `PR_FIELDS`. That list exists because a review needs all ten of them, and its
|
|
6909
6914
|
* docblock warns that trimming it fails silently rather than loudly. Sharing it here would put a
|
|
6910
|
-
*
|
|
6915
|
+
* ten-field query on a five-minute timer across the whole working set, and would put the watcher
|
|
6911
6916
|
* in a position to "optimise" a list that must never shrink.
|
|
6912
6917
|
*
|
|
6913
6918
|
* `state` rides on the same call rather than being a second one, which keeps the watcher's rate
|
|
@@ -6930,7 +6935,7 @@ var STATES = /* @__PURE__ */ new Map([
|
|
|
6930
6935
|
* about a request GitHub declined to describe, once per cycle, indefinitely.
|
|
6931
6936
|
*/
|
|
6932
6937
|
function readHeadSha(raw) {
|
|
6933
|
-
const sha = text(raw?.headRefOid);
|
|
6938
|
+
const sha = text$1(raw?.headRefOid);
|
|
6934
6939
|
if (!sha) return {
|
|
6935
6940
|
kind: "failed",
|
|
6936
6941
|
detail: "GitHub reported no head commit for this request."
|
|
@@ -6938,7 +6943,7 @@ function readHeadSha(raw) {
|
|
|
6938
6943
|
return {
|
|
6939
6944
|
kind: "head",
|
|
6940
6945
|
sha,
|
|
6941
|
-
state: STATES.get(text(raw?.state)) ?? null
|
|
6946
|
+
state: STATES.get(text$1(raw?.state)) ?? null
|
|
6942
6947
|
};
|
|
6943
6948
|
}
|
|
6944
6949
|
async function headOf(repo, number, env, cwd) {
|
|
@@ -6973,8 +6978,8 @@ async function resolve$1(repo, reference, env, cwd) {
|
|
|
6973
6978
|
} catch (error) {
|
|
6974
6979
|
throw new TargetError(`${reference} could not be looked up on GitHub: ${error.message}`);
|
|
6975
6980
|
}
|
|
6976
|
-
const headRef = text(raw.headRefName);
|
|
6977
|
-
const headSha = text(raw.headRefOid);
|
|
6981
|
+
const headRef = text$1(raw.headRefName);
|
|
6982
|
+
const headSha = text$1(raw.headRefOid);
|
|
6978
6983
|
if (!headRef || !headSha) throw new TargetError(`${reference} was found on GitHub but reported no branch and commit.`);
|
|
6979
6984
|
const resolved = {
|
|
6980
6985
|
reference,
|
|
@@ -6982,12 +6987,12 @@ async function resolve$1(repo, reference, env, cwd) {
|
|
|
6982
6987
|
headSha,
|
|
6983
6988
|
provider: GITHUB_FORGE_ID
|
|
6984
6989
|
};
|
|
6985
|
-
const baseRef = text(raw.baseRefName);
|
|
6990
|
+
const baseRef = text$1(raw.baseRefName);
|
|
6986
6991
|
if (baseRef) resolved.baseRef = baseRef;
|
|
6987
|
-
const baseSha = text(raw.baseRefOid);
|
|
6992
|
+
const baseSha = text$1(raw.baseRefOid);
|
|
6988
6993
|
if (baseSha) resolved.baseSha = baseSha;
|
|
6989
6994
|
if (typeof raw.changedFiles === "number") resolved.changedFiles = raw.changedFiles;
|
|
6990
|
-
const url = text(raw.url);
|
|
6995
|
+
const url = text$1(raw.url);
|
|
6991
6996
|
if (url) resolved.url = url;
|
|
6992
6997
|
if (typeof raw.number === "number") resolved.number = raw.number;
|
|
6993
6998
|
return resolved;
|
|
@@ -9081,7 +9086,7 @@ Follow-up Prompts:
|
|
|
9081
9086
|
${buildFollowUpPromptsSpec()}
|
|
9082
9087
|
|
|
9083
9088
|
PR Title: ${raw.pullRequest.title}
|
|
9084
|
-
${raw.pullRequest.body ? `\
|
|
9089
|
+
${raw.pullRequest.body ? `\n${raw.pullRequest.bodySource === "commit-subjects" ? "Commit subjects (there is no PR Description)" : "PR Description"}:\n${raw.pullRequest.body}\n` : ""}
|
|
9085
9090
|
${diff}`;
|
|
9086
9091
|
const overheadChars = assemble({
|
|
9087
9092
|
diff: "",
|
|
@@ -9390,10 +9395,304 @@ function normalizeSections(value) {
|
|
|
9390
9395
|
return sections;
|
|
9391
9396
|
}
|
|
9392
9397
|
//#endregion
|
|
9398
|
+
//#region ../../packages/review-harness/src/domain/ai_review/author_description.ts
|
|
9399
|
+
/**
|
|
9400
|
+
* Reading a pull request's description for whether it says anything, without ever changing it.
|
|
9401
|
+
*
|
|
9402
|
+
* Nothing here returns a copy to store or show. The description is shown verbatim, so a cleaned
|
|
9403
|
+
* copy is only ever a means of asking a question of it: whether it says anything, and whether a
|
|
9404
|
+
* quote is something the author said.
|
|
9405
|
+
*/
|
|
9406
|
+
/**
|
|
9407
|
+
* Lines that are a pull request template's scaffolding when they stand alone.
|
|
9408
|
+
*
|
|
9409
|
+
* Matched against a whole line only, after its heading marker, bold and trailing colon are gone.
|
|
9410
|
+
* A heading that says something of its own, `## Fix startup crashes`, is not a label and survives.
|
|
9411
|
+
*/
|
|
9412
|
+
var TEMPLATE_LABELS = /* @__PURE__ */ new Set([
|
|
9413
|
+
"description",
|
|
9414
|
+
"summary",
|
|
9415
|
+
"changes",
|
|
9416
|
+
"what",
|
|
9417
|
+
"why",
|
|
9418
|
+
"how",
|
|
9419
|
+
"context",
|
|
9420
|
+
"motivation",
|
|
9421
|
+
"motivation and context",
|
|
9422
|
+
"what changed",
|
|
9423
|
+
"what does this pr do",
|
|
9424
|
+
"why is this needed",
|
|
9425
|
+
"type of change",
|
|
9426
|
+
"testing",
|
|
9427
|
+
"tests",
|
|
9428
|
+
"test plan",
|
|
9429
|
+
"how to test",
|
|
9430
|
+
"how has this been tested",
|
|
9431
|
+
"screenshots",
|
|
9432
|
+
"notes",
|
|
9433
|
+
"checklist",
|
|
9434
|
+
"related issues",
|
|
9435
|
+
"related issue",
|
|
9436
|
+
"links"
|
|
9437
|
+
]);
|
|
9438
|
+
var HTML_COMMENT = /<!--[\s\S]*?-->/g;
|
|
9439
|
+
var HEADING_MARKER = /^#{1,6}\s*/;
|
|
9440
|
+
var UNCHECKED_TASK = /^[-*+]\s+\[ \]/;
|
|
9441
|
+
var EMPTY_LIST_ITEM = /^(?:[-*+]|\d+[.)])\s*$/;
|
|
9442
|
+
function asLabel(line) {
|
|
9443
|
+
return line.replace(HEADING_MARKER, "").replace(/^\*\*(.*)\*\*$/, "$1").replace(/^__(.*)__$/, "$1").replace(/:\s*$/, "").trim().toLowerCase();
|
|
9444
|
+
}
|
|
9445
|
+
/** One line, reduced to whatever in it is the author's own words. Empty when none are. */
|
|
9446
|
+
function substance(line) {
|
|
9447
|
+
const trimmed = line.trim();
|
|
9448
|
+
if (!trimmed || UNCHECKED_TASK.test(trimmed) || EMPTY_LIST_ITEM.test(trimmed)) return "";
|
|
9449
|
+
if (TEMPLATE_LABELS.has(asLabel(trimmed))) return "";
|
|
9450
|
+
return trimmed.replace(HEADING_MARKER, "");
|
|
9451
|
+
}
|
|
9452
|
+
/**
|
|
9453
|
+
* The parts of a description the author wrote, one line each, with the template's scaffolding gone.
|
|
9454
|
+
*
|
|
9455
|
+
* Only HTML comments, unticked checklist items, empty list items and bare template labels are
|
|
9456
|
+
* discounted. A ticked item is the author asserting something, so it stays.
|
|
9457
|
+
*/
|
|
9458
|
+
function authorText(description) {
|
|
9459
|
+
if (!description) return "";
|
|
9460
|
+
return description.replace(HTML_COMMENT, "").split("\n").map(substance).filter((line) => line !== "").join("\n");
|
|
9461
|
+
}
|
|
9462
|
+
/**
|
|
9463
|
+
* Whether a description has anything in it beyond an unfilled template.
|
|
9464
|
+
*
|
|
9465
|
+
* Conservative by design: any line that survives counts, so a sparse description is shown rather
|
|
9466
|
+
* than hidden.
|
|
9467
|
+
*/
|
|
9468
|
+
function hasAuthorContent(description) {
|
|
9469
|
+
return authorText(description) !== "";
|
|
9470
|
+
}
|
|
9471
|
+
function isLongDescription(description) {
|
|
9472
|
+
const trimmed = description.trim();
|
|
9473
|
+
return trimmed.split("\n").length > 10 || trimmed.length > 700;
|
|
9474
|
+
}
|
|
9475
|
+
//#endregion
|
|
9476
|
+
//#region ../../packages/review-harness/src/domain/ai_review/motivation.ts
|
|
9477
|
+
var MOTIVATION_STATUSES = [
|
|
9478
|
+
"stated",
|
|
9479
|
+
"inferred",
|
|
9480
|
+
"unclear"
|
|
9481
|
+
];
|
|
9482
|
+
var MOTIVATION_EVIDENCE_KINDS = [
|
|
9483
|
+
"test",
|
|
9484
|
+
"behaviour",
|
|
9485
|
+
"issue",
|
|
9486
|
+
"commit",
|
|
9487
|
+
"docs",
|
|
9488
|
+
"code"
|
|
9489
|
+
];
|
|
9490
|
+
/** Ours, not the model's: what an unsupported claim is replaced with when a question remains. */
|
|
9491
|
+
var UNESTABLISHED_MOTIVATION = "The reason for this change isn't established by the description or the code.";
|
|
9492
|
+
/** Stable, so "Mark answered" on it survives a reload like any other question's. */
|
|
9493
|
+
var INTENT_QUESTION_ID = "intent-motivation";
|
|
9494
|
+
/**
|
|
9495
|
+
* Fewer words than this cannot attribute a reason to anyone: "the cache" is in most descriptions
|
|
9496
|
+
* that mention a cache. An initial setting, not a measured one.
|
|
9497
|
+
*/
|
|
9498
|
+
var MIN_QUOTE_WORDS = 3;
|
|
9499
|
+
function text(value) {
|
|
9500
|
+
if (typeof value !== "string") return void 0;
|
|
9501
|
+
return value.trim() || void 0;
|
|
9502
|
+
}
|
|
9503
|
+
function normalizeEvidence(value) {
|
|
9504
|
+
if (!Array.isArray(value)) return [];
|
|
9505
|
+
return value.flatMap((raw) => {
|
|
9506
|
+
const item = raw ?? {};
|
|
9507
|
+
const kind = item.kind;
|
|
9508
|
+
const detail = text(item.detail);
|
|
9509
|
+
if (!MOTIVATION_EVIDENCE_KINDS.includes(kind) || !detail) return [];
|
|
9510
|
+
const file = text(item.file);
|
|
9511
|
+
return [{
|
|
9512
|
+
kind,
|
|
9513
|
+
detail,
|
|
9514
|
+
...file ? { file } : {}
|
|
9515
|
+
}];
|
|
9516
|
+
});
|
|
9517
|
+
}
|
|
9518
|
+
/** The model's `motivation`, if it is one at all. Absent, or the wrong shape, is no motivation. */
|
|
9519
|
+
function normalizeMotivation(value) {
|
|
9520
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
9521
|
+
const item = value;
|
|
9522
|
+
const status = item.status;
|
|
9523
|
+
const summary = text(item.summary);
|
|
9524
|
+
if (!MOTIVATION_STATUSES.includes(status) || !summary) return void 0;
|
|
9525
|
+
const sourceQuote = text(item.sourceQuote);
|
|
9526
|
+
const authorQuestion = text(item.authorQuestion);
|
|
9527
|
+
return {
|
|
9528
|
+
status,
|
|
9529
|
+
summary,
|
|
9530
|
+
evidence: normalizeEvidence(item.evidence),
|
|
9531
|
+
...sourceQuote ? { sourceQuote } : {},
|
|
9532
|
+
...authorQuestion ? { authorQuestion } : {}
|
|
9533
|
+
};
|
|
9534
|
+
}
|
|
9535
|
+
/** Whitespace is the one thing a faithful quote may differ in: line wrapping is not wording. */
|
|
9536
|
+
function collapse(value) {
|
|
9537
|
+
return value.replace(/\s+/g, " ").trim();
|
|
9538
|
+
}
|
|
9539
|
+
/**
|
|
9540
|
+
* Whether the author wrote this, in these words, in the description the analysis read.
|
|
9541
|
+
*
|
|
9542
|
+
* Searched in the author's own text only. A template's instructions are in the description too, and
|
|
9543
|
+
* "Explain why this change is needed" would otherwise pass as the author explaining why.
|
|
9544
|
+
*/
|
|
9545
|
+
function isAuthorQuote(quote, description) {
|
|
9546
|
+
if (!quote) return false;
|
|
9547
|
+
const needle = collapse(quote);
|
|
9548
|
+
if (needle.split(" ").length < MIN_QUOTE_WORDS) return false;
|
|
9549
|
+
return collapse(authorText(description)).includes(needle);
|
|
9550
|
+
}
|
|
9551
|
+
/**
|
|
9552
|
+
* The checks, applied to one candidate.
|
|
9553
|
+
*
|
|
9554
|
+
* Each status has one thing it cannot stand without: a verified quote, evidence, a question. The
|
|
9555
|
+
* fields that belong to the other statuses are dropped rather than carried, so a `stated` motive
|
|
9556
|
+
* cannot also show a guess and an `unclear` one cannot also show a quote.
|
|
9557
|
+
*
|
|
9558
|
+
* A claim that fails becomes unclear only if the model also asked the question that would settle
|
|
9559
|
+
* it, and then in our words rather than its own. Otherwise there is nothing to show that the
|
|
9560
|
+
* reviewer could act on, and nothing is shown.
|
|
9561
|
+
*/
|
|
9562
|
+
function verifyMotivation(candidate, authorDescription) {
|
|
9563
|
+
if (!candidate) return {};
|
|
9564
|
+
const base = {
|
|
9565
|
+
contractVersion: 1,
|
|
9566
|
+
summary: candidate.summary
|
|
9567
|
+
};
|
|
9568
|
+
if (candidate.status === "stated" && isAuthorQuote(candidate.sourceQuote, authorDescription)) return { motivation: {
|
|
9569
|
+
...base,
|
|
9570
|
+
status: "stated",
|
|
9571
|
+
sourceQuote: candidate.sourceQuote
|
|
9572
|
+
} };
|
|
9573
|
+
if (candidate.status === "inferred" && candidate.evidence.length > 0) return { motivation: {
|
|
9574
|
+
...base,
|
|
9575
|
+
status: "inferred",
|
|
9576
|
+
evidence: candidate.evidence
|
|
9577
|
+
} };
|
|
9578
|
+
if (candidate.status === "unclear" && candidate.authorQuestion) return { motivation: {
|
|
9579
|
+
...base,
|
|
9580
|
+
status: "unclear",
|
|
9581
|
+
authorQuestion: candidate.authorQuestion
|
|
9582
|
+
} };
|
|
9583
|
+
const reason = candidate.status === "stated" ? candidate.sourceQuote ? "unverified-quote" : "missing-quote" : candidate.status === "inferred" ? "no-evidence" : "no-question";
|
|
9584
|
+
const correction = {
|
|
9585
|
+
status: candidate.status,
|
|
9586
|
+
summary: candidate.summary,
|
|
9587
|
+
reason
|
|
9588
|
+
};
|
|
9589
|
+
if (candidate.status !== "unclear" && candidate.authorQuestion) return {
|
|
9590
|
+
motivation: {
|
|
9591
|
+
contractVersion: 1,
|
|
9592
|
+
status: "unclear",
|
|
9593
|
+
summary: UNESTABLISHED_MOTIVATION,
|
|
9594
|
+
authorQuestion: candidate.authorQuestion
|
|
9595
|
+
},
|
|
9596
|
+
correction: {
|
|
9597
|
+
...correction,
|
|
9598
|
+
outcome: "downgraded"
|
|
9599
|
+
}
|
|
9600
|
+
};
|
|
9601
|
+
return { correction: {
|
|
9602
|
+
...correction,
|
|
9603
|
+
outcome: "withheld"
|
|
9604
|
+
} };
|
|
9605
|
+
}
|
|
9606
|
+
/**
|
|
9607
|
+
* The one evidence-link policy, for both hosts.
|
|
9608
|
+
*
|
|
9609
|
+
* A changed file is a diff link and a real unchanged file a source link. A path the host can show
|
|
9610
|
+
* does not exist is removed and returned for the audit. A path the host cannot check keeps its
|
|
9611
|
+
* text and gets no state, so it is never presented as a link the reader can trust.
|
|
9612
|
+
*/
|
|
9613
|
+
function placeEvidenceFiles(motivation, paths) {
|
|
9614
|
+
if (!motivation?.evidence) return {
|
|
9615
|
+
motivation,
|
|
9616
|
+
removed: []
|
|
9617
|
+
};
|
|
9618
|
+
const removed = [];
|
|
9619
|
+
const evidence = motivation.evidence.map(({ fileState: _ignored, ...item }) => {
|
|
9620
|
+
if (!item.file) return item;
|
|
9621
|
+
if (paths.changed(item.file)) return {
|
|
9622
|
+
...item,
|
|
9623
|
+
fileState: "changed"
|
|
9624
|
+
};
|
|
9625
|
+
if (!paths.exists) return item;
|
|
9626
|
+
if (paths.exists(item.file)) return {
|
|
9627
|
+
...item,
|
|
9628
|
+
fileState: "unchanged"
|
|
9629
|
+
};
|
|
9630
|
+
removed.push(item.file);
|
|
9631
|
+
const { file: _gone, ...rest } = item;
|
|
9632
|
+
return rest;
|
|
9633
|
+
});
|
|
9634
|
+
return {
|
|
9635
|
+
motivation: {
|
|
9636
|
+
...motivation,
|
|
9637
|
+
evidence
|
|
9638
|
+
},
|
|
9639
|
+
removed
|
|
9640
|
+
};
|
|
9641
|
+
}
|
|
9642
|
+
/** Two questions are the same question when they differ only in case, spacing or end punctuation. */
|
|
9643
|
+
function sameQuestion(a, b) {
|
|
9644
|
+
const key = (value) => collapse(value).toLowerCase().replace(/[?.!\s]+$/, "");
|
|
9645
|
+
return key(a) === key(b);
|
|
9646
|
+
}
|
|
9647
|
+
/**
|
|
9648
|
+
* File an unclear motive's question with the rest, as the one Intent question.
|
|
9649
|
+
*
|
|
9650
|
+
* A question the model already asked in the same words is tagged rather than repeated, so the
|
|
9651
|
+
* reader meets it once, in the Intent group. Telling two differently worded questions apart is
|
|
9652
|
+
* judgement and is left to the prompt.
|
|
9653
|
+
*/
|
|
9654
|
+
function withIntentQuestion(questions, motivation) {
|
|
9655
|
+
if (motivation?.status !== "unclear" || !motivation.authorQuestion) return questions;
|
|
9656
|
+
const asked = motivation.authorQuestion;
|
|
9657
|
+
const existing = questions.findIndex((q) => sameQuestion(q.question, asked));
|
|
9658
|
+
if (existing >= 0) return questions.map((q, index) => index === existing ? {
|
|
9659
|
+
...q,
|
|
9660
|
+
category: "intent"
|
|
9661
|
+
} : q);
|
|
9662
|
+
const taken = new Set(questions.map((q) => q.id));
|
|
9663
|
+
let id = INTENT_QUESTION_ID;
|
|
9664
|
+
for (let n = 2; taken.has(id); n += 1) id = `${INTENT_QUESTION_ID}-${n}`;
|
|
9665
|
+
return [...questions, {
|
|
9666
|
+
id,
|
|
9667
|
+
question: asked,
|
|
9668
|
+
category: "intent"
|
|
9669
|
+
}];
|
|
9670
|
+
}
|
|
9671
|
+
/** The candidate checked and, when it ends unclear, its question filed. */
|
|
9672
|
+
function applyMotivation(result, candidate, authorDescription) {
|
|
9673
|
+
const { motivation, correction } = verifyMotivation(candidate, authorDescription);
|
|
9674
|
+
const { motivation: _unverified, ...rest } = result;
|
|
9675
|
+
return {
|
|
9676
|
+
result: {
|
|
9677
|
+
...rest,
|
|
9678
|
+
...motivation ? { motivation } : {},
|
|
9679
|
+
questions: withIntentQuestion(result.questions, motivation)
|
|
9680
|
+
},
|
|
9681
|
+
...correction ? { correction } : {}
|
|
9682
|
+
};
|
|
9683
|
+
}
|
|
9684
|
+
//#endregion
|
|
9393
9685
|
//#region ../../packages/review-harness/src/domain/ai_review/pipeline.ts
|
|
9394
9686
|
var STRUCTURED_OUTPUT_INSTRUCTIONS = `In addition to the review principles above, first understand the implementation, then respond with a single JSON object (no markdown, no surrounding text) matching exactly this shape:
|
|
9395
9687
|
{
|
|
9396
9688
|
"summary": string,
|
|
9689
|
+
"motivation"?: {
|
|
9690
|
+
"status": "stated" | "inferred" | "unclear",
|
|
9691
|
+
"summary": string,
|
|
9692
|
+
"sourceQuote"?: string,
|
|
9693
|
+
"evidence"?: [{ "kind": "test" | "behaviour" | "issue" | "commit" | "docs" | "code", "detail": string, "file"?: string }],
|
|
9694
|
+
"authorQuestion"?: string
|
|
9695
|
+
},
|
|
9397
9696
|
"overallRisk": "low" | "medium" | "high",
|
|
9398
9697
|
"sections": [{
|
|
9399
9698
|
"id": string,
|
|
@@ -9416,6 +9715,11 @@ var STRUCTURED_OUTPUT_INSTRUCTIONS = `In addition to the review principles above
|
|
|
9416
9715
|
}
|
|
9417
9716
|
|
|
9418
9717
|
Field guidance:
|
|
9718
|
+
- "motivation": WHY this change exists, which is a different question from what it does ("summary" covers that). Name the problem being solved or the goal being served, never the mechanics: "adds a retry loop" is what the code does, "the sync job was failing on transient network errors" is why. Weigh sources in this order: the PR Description and any issue it links; then tests and user-visible behaviour; then commit messages and docs; then the implementation itself. Choose exactly one "status":
|
|
9719
|
+
- "stated": the author's own words in the PR Description say why. "sourceQuote" is REQUIRED: a short passage (one sentence or less, at least three words) copied character for character from the PR Description, which says why. Only the PR Description counts as the author stating it. Never use "stated" when the description only restates what the code does.
|
|
9720
|
+
- "inferred": nothing states it, but the change gives real evidence of it. "evidence" is REQUIRED and non-empty: each entry names what points to the motive, such as a test asserting the fixed behaviour, an issue reference, or a commit message, with "file" set to the changed file it is in where there is one. Never infer a motive from names alone (a branch, a function or a file being called "fix-x" is not evidence). Word the summary as a likely reading, not a fact: "This appears to..." rather than "This fixes...".
|
|
9721
|
+
- "unclear": neither the description nor the change establishes why, AND knowing why would change how this change should be reviewed (for example, whether a behaviour change is intended). "authorQuestion" is REQUIRED: one focused question for the author that would settle it. Do not also ask that question, in any wording, in "questions".
|
|
9722
|
+
If the motive is unclear and knowing it would not change the review, omit "motivation" entirely. Do not add it just to fill the field.
|
|
9419
9723
|
- "sections": partition the pull request into logical implementation sections (e.g. "Authentication", "Database Migration", "API Changes"), grouped by responsibility/purpose, not by directory or file type. Identify a sensible number of sections — most PRs will naturally have between 2 and 8, but do not invent or split sections simply to reach a target; let the actual structure of the change decide the count. For each section: "oneLineSummary" is a single sentence; "purpose" explains why this part of the change exists; "whyReview" is a short note aimed at the reviewer about what to focus on; "checklist" is a short list of things to verify (review prompts, not findings — never report bugs, code smells, or recommendations inside a section, that's what "issues" is for). Assign "order" starting at 1 in the sequence a reviewer should walk through the sections (narrative order, not file count or alphabetical). "reviewWeight" is a relative time-budget indicator from 1 (skim) to 5 (spend the most time here), independent of "priority" and "reviewAttention". Set "recommendedStartingPoint": true on exactly one section — whichever one a reviewer should actually read first for the rest of the review to make sense. This is not necessarily the section with "order": 1 (e.g. a foundational database migration might naturally come first while a reviewer should still start with the authentication logic it enables). Set it to false on every other section. Every file in the diff must appear in exactly one section — the sections are the only file list the developer sees, so an omitted file is a file they never review. Files with no natural home (lockfiles, dependency bumps, generated output, formatting-only changes) go in a single final catch-all section — title it for what it contains, e.g. "Supporting changes" or "Dependency and config updates" — with "priority": "supporting" and "reviewAttention": "low". Never drop a file just because it is uninteresting; say that it is uninteresting instead.
|
|
9420
9724
|
- "files" inside a section: "path" is the file path exactly as it appears in the diff. "focus" is REQUIRED on every file — if a file is worth listing in a section, it is worth explaining why the reviewer has been brought to it. Keep it to one or two short sentences, roughly 20-35 words, never more than two sentences. Make it actionable rather than descriptive: name the role this specific file plays in the section, then say what the reviewer should verify there. For example: "Introduces the new association reader. Verify it preserves the existing lookup behaviour." / "Implements the retry path. Confirm error handling matches the existing gateway implementation." / "Registers the generated behaviour with Active Record. Check the hook fires for subclasses too." Do not restate the section's summary or checklist, do not use bullet points, and do not write filler such as "Review this file carefully."
|
|
9421
9725
|
- "file" on an issue or question: prefer anchoring a finding to the changed file responsible for the concern, named exactly as it appears in the diff. If an unchanged repository file is necessary context and no changed-file location accurately represents the finding, it may be referenced as an unchanged related file. Never list an unchanged file in a section's "files" array, and never describe an unchanged file as part of the pull request.
|
|
@@ -9426,7 +9730,7 @@ Field guidance:
|
|
|
9426
9730
|
- "existingDiscussion" on an issue: set this only when a thread in the PR DISCUSSION CONTEXT block already raises this same concern, and you have independently confirmed from the diff that it still applies. "threadId" must be copied verbatim from that thread's [T_...] marker — never invent an id, never guess, and omit the field entirely if no thread matches. Set "relation" to "same" when the thread already makes this exact point, or "related" when it discusses this area without covering the finding — a reviewer acts differently on "someone said this already" than on "someone was looking near here". Still write the issue normally; this field only records that the point was raised before, so the reviewer is not shown it as a fresh discovery. Do not use it to pad the review with issues you would otherwise have left out.
|
|
9427
9731
|
- "confidence" on an issue: how sure you are the issue is real, on the same high/medium/low scale as a question's "importance". This is a separate axis from "severity", which is how much it would matter if real — a critical issue you are unsure about and a nit you are certain of are different things to act on, so do not let one field stand in for the other. Use LOW when you are reasoning from a diff excerpt without the surrounding code, and say what would confirm it in the description. Omit the field rather than guessing a middle value.
|
|
9428
9732
|
- "questions": each one is a genuine open item the reviewer needs answered before they can sign off, not a rhetorical prompt. "importance" is how much attention it deserves, on the same high/medium/low scale as a section's "reviewAttention" (e.g. a question about a possibly-exposed secret is "high"; a naming preference is "low"). "whyThisMatters" is 1-2 sentences explaining the concrete consequence of the question going unanswered — not a restatement of the question itself.
|
|
9429
|
-
- Every section's "oneLineSummary"/"purpose"/"whyReview", each file's "focus",
|
|
9733
|
+
- Every section's "oneLineSummary"/"purpose"/"whyReview", each file's "focus", each question's "whyThisMatters", and every text field inside "motivation" are rendered as plain prose paragraphs, not markdown — write them as normal sentences. Do not use "- " bullet points or numbered lists inside these fields; if you're enumerating several points, join them into flowing sentences instead. Inline code/identifiers may use backtick spans (e.g. \`ErrorSeverity\`) — those do render.
|
|
9430
9734
|
- "summary", "recommendation" and each "nextSteps" entry are rendered as Markdown, so use it where it carries meaning: backtick spans for identifiers, a fenced block for a snippet worth reading as code, a short bullet list where the content genuinely is a list. Prose is still the default and these fields are still short. Do not add headings, and do not turn a paragraph into bullets to look organised.
|
|
9431
9735
|
|
|
9432
9736
|
This JSON object is the only output the developer sees. The free-text sections above (Overview, Guide, Issues, Questions, Follow-up Prompts) are for your own internal reasoning — do not copy them verbatim into any JSON field.`;
|
|
@@ -9444,7 +9748,22 @@ function buildStructuredReviewPrompt(pr, options = {}) {
|
|
|
9444
9748
|
partialFiles: partialFiles ?? []
|
|
9445
9749
|
};
|
|
9446
9750
|
}
|
|
9447
|
-
|
|
9751
|
+
/**
|
|
9752
|
+
* A reply, parsed, with the motivation checked against the description the analysis read.
|
|
9753
|
+
*
|
|
9754
|
+
* The check happens here rather than after, so there is no parsed result anywhere whose motivation
|
|
9755
|
+
* has not been through it. `correction` says what the check refused, for a caller with an audit to
|
|
9756
|
+
* put it in.
|
|
9757
|
+
*/
|
|
9758
|
+
function parseReviewResponse(raw, authorDescription) {
|
|
9759
|
+
const { result, motivation } = parseUnchecked(raw);
|
|
9760
|
+
const applied = applyMotivation(result, motivation, authorDescription);
|
|
9761
|
+
return {
|
|
9762
|
+
result: applied.result,
|
|
9763
|
+
...applied.correction ? { motivationCorrection: applied.correction } : {}
|
|
9764
|
+
};
|
|
9765
|
+
}
|
|
9766
|
+
function parseUnchecked(raw) {
|
|
9448
9767
|
const jsonText = extractJsonText(raw);
|
|
9449
9768
|
let parsed;
|
|
9450
9769
|
try {
|
|
@@ -9458,14 +9777,17 @@ function parseReviewResult(raw) {
|
|
|
9458
9777
|
const recommendation = asString(obj.recommendation);
|
|
9459
9778
|
if (!summary && !recommendation && !Array.isArray(obj.issues) && !Array.isArray(obj.nextSteps)) throw new Error("The AI returned valid JSON, but not in the expected analysis shape (missing summary/recommendation/issues/nextSteps). This usually means the model ignored the requested schema.");
|
|
9460
9779
|
return {
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
|
|
9464
|
-
|
|
9465
|
-
|
|
9466
|
-
|
|
9467
|
-
|
|
9468
|
-
|
|
9780
|
+
result: {
|
|
9781
|
+
schemaVersion: 1,
|
|
9782
|
+
summary,
|
|
9783
|
+
overallRisk: RISKS.includes(obj.overallRisk) ? obj.overallRisk : "medium",
|
|
9784
|
+
sections: normalizeSections(obj.sections),
|
|
9785
|
+
issues: normalizeIssues(obj.issues),
|
|
9786
|
+
questions: normalizeQuestions(obj.questions),
|
|
9787
|
+
recommendation,
|
|
9788
|
+
nextSteps: asStringArray(obj.nextSteps)
|
|
9789
|
+
},
|
|
9790
|
+
motivation: normalizeMotivation(obj.motivation)
|
|
9469
9791
|
};
|
|
9470
9792
|
}
|
|
9471
9793
|
var EXPLAIN_SIMPLY_PROMPT = "Explain this review in plain English for someone unfamiliar with this codebase. Avoid jargon, use a simple everyday analogy if it helps, and keep it skippable — a couple of short paragraphs is enough.";
|
|
@@ -9836,6 +10158,7 @@ async function assemble(changeSet, budget, lens, prContext) {
|
|
|
9836
10158
|
...changeSet.data,
|
|
9837
10159
|
files: selection.inline,
|
|
9838
10160
|
changedFileCount: allFiles.length,
|
|
10161
|
+
...prContext ? { pullRequest: pullRequestFor(changeSet.data.pullRequest, prContext) } : {},
|
|
9839
10162
|
discussion: prContext?.discussion
|
|
9840
10163
|
}, selection.inline.length, null);
|
|
9841
10164
|
const shared = {
|
|
@@ -9865,6 +10188,21 @@ async function assemble(changeSet, budget, lens, prContext) {
|
|
|
9865
10188
|
};
|
|
9866
10189
|
}
|
|
9867
10190
|
/**
|
|
10191
|
+
* The change's pull request, as the prompt should present it once a forge request is attached.
|
|
10192
|
+
*
|
|
10193
|
+
* With no description, the commit subjects stay: they are still the best account of the change
|
|
10194
|
+
* there is, and still labelled as commits rather than as the author's words.
|
|
10195
|
+
*/
|
|
10196
|
+
function pullRequestFor(local, prContext) {
|
|
10197
|
+
const body = prContext.pullRequest.body;
|
|
10198
|
+
if (!body) return local;
|
|
10199
|
+
const { bodySource: _commits, ...rest } = local;
|
|
10200
|
+
return {
|
|
10201
|
+
...rest,
|
|
10202
|
+
body
|
|
10203
|
+
};
|
|
10204
|
+
}
|
|
10205
|
+
/**
|
|
9868
10206
|
* The inline patches as one blob, for grounding the assistant.
|
|
9869
10207
|
*
|
|
9870
10208
|
* No ceiling of its own. `selectInlineDiffs` already bounded this, and a second constant guarding
|
|
@@ -10199,13 +10537,14 @@ async function existingPaths(repoPath, paths, commit = "HEAD") {
|
|
|
10199
10537
|
* from the change set, and it costs one git call, or none when the guide cites nothing off-diff.
|
|
10200
10538
|
*/
|
|
10201
10539
|
async function validateGuide(rawResponse, context) {
|
|
10202
|
-
const result =
|
|
10540
|
+
const { result, motivationCorrection } = parseReviewResponse(rawResponse, context.prContext?.pullRequest.body);
|
|
10203
10541
|
const changedPaths = new Set(context.changeSet.data.files.map((file) => file.name));
|
|
10204
10542
|
const sectionIds = new Set(result.sections.map((section) => section.id));
|
|
10205
10543
|
const findings = [...result.issues, ...result.questions];
|
|
10206
10544
|
const offDiff = /* @__PURE__ */ new Set();
|
|
10207
10545
|
for (const section of result.sections) for (const file of section.files) if (!changedPaths.has(file.path)) offDiff.add(file.path);
|
|
10208
10546
|
for (const finding of findings) if (finding.file && !changedPaths.has(finding.file)) offDiff.add(finding.file);
|
|
10547
|
+
for (const evidence of result.motivation?.evidence ?? []) if (evidence.file && !changedPaths.has(evidence.file)) offDiff.add(evidence.file);
|
|
10209
10548
|
const realButUnchanged = await existingPaths(context.changeSet.repoPath, [...offDiff]);
|
|
10210
10549
|
const unknownPaths = /* @__PURE__ */ new Set();
|
|
10211
10550
|
const sectioned = /* @__PURE__ */ new Set();
|
|
@@ -10246,6 +10585,13 @@ async function validateGuide(rawResponse, context) {
|
|
|
10246
10585
|
orphanedSectionRefs += 1;
|
|
10247
10586
|
}
|
|
10248
10587
|
}
|
|
10588
|
+
const placed = placeEvidenceFiles(result.motivation, {
|
|
10589
|
+
changed: (path) => changedPaths.has(path),
|
|
10590
|
+
exists: (path) => realButUnchanged.has(path)
|
|
10591
|
+
});
|
|
10592
|
+
if (placed.motivation) result.motivation = placed.motivation;
|
|
10593
|
+
for (const path of placed.removed) unknownPaths.add(path);
|
|
10594
|
+
for (const evidence of placed.motivation?.evidence ?? []) if (evidence.fileState === "unchanged" && evidence.file) noteReferenced(evidence.file);
|
|
10249
10595
|
const attribution = attributeIssuesToDiscussion(result, context.processed.discussion);
|
|
10250
10596
|
return {
|
|
10251
10597
|
result: attribution.result,
|
|
@@ -10253,7 +10599,8 @@ async function validateGuide(rawResponse, context) {
|
|
|
10253
10599
|
unknownPaths: [...unknownPaths].sort(),
|
|
10254
10600
|
relocatedFindings,
|
|
10255
10601
|
orphanedSectionRefs,
|
|
10256
|
-
rejectedAttributions: attribution.rejected
|
|
10602
|
+
rejectedAttributions: attribution.rejected,
|
|
10603
|
+
...motivationCorrection ? { motivationCorrection } : {}
|
|
10257
10604
|
},
|
|
10258
10605
|
referencedFiles: [...referenced].map(([path, sections]) => ({
|
|
10259
10606
|
path,
|
|
@@ -12317,6 +12664,7 @@ function movable(fetched) {
|
|
|
12317
12664
|
};
|
|
12318
12665
|
copy("headSha");
|
|
12319
12666
|
copy("title");
|
|
12667
|
+
copy("body");
|
|
12320
12668
|
copy("filesUrl");
|
|
12321
12669
|
copy("fileUrlTemplate");
|
|
12322
12670
|
copy("author");
|
|
@@ -12526,6 +12874,7 @@ async function reconcile(workspace, runners, reviewedSha, remote, conversationOk
|
|
|
12526
12874
|
const result = {
|
|
12527
12875
|
schemaVersion: 1,
|
|
12528
12876
|
summary: parsed.summary || previous.summary,
|
|
12877
|
+
...previous.motivation ? { motivation: previous.motivation } : {},
|
|
12529
12878
|
overallRisk: parsed.overallRisk ?? previous.overallRisk,
|
|
12530
12879
|
recommendation: parsed.recommendation || previous.recommendation,
|
|
12531
12880
|
nextSteps: parsed.nextSteps.length ? parsed.nextSteps : previous.nextSteps,
|
|
@@ -13084,4 +13433,4 @@ function lineageIds(id) {
|
|
|
13084
13433
|
return [.../* @__PURE__ */ new Set([id, ...lineage.map((review) => review.id)])];
|
|
13085
13434
|
}
|
|
13086
13435
|
//#endregion
|
|
13087
|
-
export {
|
|
13436
|
+
export { relativeTime as $, AGENT_IDS as $n, kissDecisionRefusal as $t, createPrState as A, readQueue as An, checkCodeFreshness as At, toneOf as B, WITHHELD_REASON as Bn, PHASES as Bt, readEvents as C, offersLifecycle as Cn, statedWorkspacePort as Ct, announceJob as D, queueAddMany as Dn, forgeFor as Dt, telemetryUploadConsent as E, queueAdd as En, forgeResolver as Et, markAllRead as F, questionsOutstanding as Fn, endedAt as Ft, isLongDescription as G, git as Gn, clearClaim as Gt, withUsageRecorded as H, resolveTarget as Hn, phasesFor as Ht, markRead as I, reviewerDispositions as In, fail as It, ReviewBeingDeletedError as J, clearAgentPreference as Jn, decideKissRecommendation as Jt, asString as K, resolveAgent as Kn, driverGone as Kt, pruneNotifications as L, processDiscussion as Ln, isTerminal as Lt, eventName as M, isIssueOutstanding as Mn, followedBranch as Mt, headline as N, isQuestionOutstanding as Nn, reviewedShaOf as Nt, branchOf as O, queueRemove as On, remoteHost as Ot, listNotifications as P, issuesOutstanding as Pn, allJobs as Pt, modelHelpLines as Q, STORE_ROOT as Qn, indexReviewGroups as Qt, readNotification as R, recommendationsOf as Rn, loadJob as Rt, startJob as S, lifecyclePill as Sn, resolveWorkspacePort as St, recordTelemetryUploadConsent as T, sectionOf as Tn, claimRequestUrl as Tt, EXPLAIN_SIMPLY_PROMPT as U, displayRef as Un, progressSteps as Ut, unreadCount as V, kissOutcome as Vn, isLiveJob as Vt, hasAuthorContent as W, isCommitReview as Wn, purposeOf as Wt, readMarker as X, writeAgentPreference as Xn, groupByLineage as Xt, MANAGED_ROOT as Y, readAgentPreference as Yn, followedRefName as Yt, removeWorktree as Z, CONFIG_PATH as Zn, indexKeptIds as Zt, agentEnvOf as _, LIFECYCLES as _n, feedbackUrl as _t, wasBlocked as a, matchingWorkspaceIds as an, AgentUnavailableError as ar, bootstrapUrl as at, reanalyseReview as b, lifecycleAction as bn, MIN_WORKSPACE_PORT as bt, RefreshUnavailableError as c, presentedKissRunOf as cn, readServerRecord as ct, refreshPrContext as d, reviewedCommit as dn, spawnServer as dt, kissDecisionsOf as en, DEFAULT_AGENT_ID as er, activateQueued as et, isUnchanged as f, reviewedRepositories as fn, stopServer as ft, runningJobs as g, workspaceStamps as gn, PACKAGE_NAME as gt, liveUpdateFor as h, workspaceRevision as hn, BUILD_VERSION as ht, liveJobsFor as i, loadWorkspace as in, AgentCancelledError as ir, readIndexToken as it, createPrUpdate as j, doneVerb as jn, checkReview as jt, subjectOf as k, queueStamp as kn, fillFileUrlTemplate as kt, mergeUpdateInto as l, previousKissResult as ln, reviewsUrl as lt, UpdateAlreadyRunningError as m, touchWorkspace as mn, writeServerRecord as mt, deleteReview as n, lineageKeyFor as nn, agentFor as nr, queueForReview as nt, discardJob as o, mutateWorkspace as on, clearServerRecord as ot, askCheckout as p, summarise as pn, workspaceUrl as pt, extractJsonText as q, resolveModel as qn, isClaimed as qt, lineageIds as r, lineagePosition as rn, detectAgents as rr, settleQueue as rt, FETCH_TIMEOUT_MS as s, positionInLineage as sn, ensureServer as st, deleteLineage as t, latestKissRun as tn, agentById as tr, compactQueue as tt, updateReview as u, recentWorkspaces as un, serverEntryExists as ut, recordKissRun as v, activityLabel as vn, DEFAULT_WORKSPACE_PORT as vt, record as w, parseLifecycle as wn, writePortPreference as wt, runJob as x, lifecycleChip as xn, parseWorkspacePort as xt, startKissJob as y, isLifecycle as yn, MAX_WORKSPACE_PORT as yt, removeNotification as z, describeAuthorship as zn, saveJob as zt };
|
package/dist/main.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $n as
|
|
2
|
+
import { $ as relativeTime, $n as AGENT_IDS, Bn as WITHHELD_REASON, Bt as PHASES, Ct as statedWorkspacePort, E as telemetryUploadConsent, Et as forgeResolver, Gn as git, Gt as clearClaim, Hn as resolveTarget, Kn as resolveAgent, Kt as driverGone, Lt as isTerminal, Pt as allJobs, Q as modelHelpLines, Qn as STORE_ROOT, Rt as loadJob, S as startJob, T as recordTelemetryUploadConsent, Un as displayRef, Ut as progressSteps, Vn as kissOutcome, Wt as purposeOf, X as readMarker, Xn as writeAgentPreference, Xt as groupByLineage, Y as MANAGED_ROOT, Yn as readAgentPreference, Yt as followedRefName, Z as removeWorktree, Zn as CONFIG_PATH, a as wasBlocked, an as matchingWorkspaceIds, at as bootstrapUrl, b as reanalyseReview, bt as MIN_WORKSPACE_PORT, er as DEFAULT_AGENT_ID, fn as reviewedRepositories, ft as stopServer, g as runningJobs, gt as PACKAGE_NAME, h as liveUpdateFor, ht as BUILD_VERSION, i as liveJobsFor, in as loadWorkspace, it as readIndexToken, jt as checkReview, l as mergeUpdateInto, lt as reviewsUrl, m as UpdateAlreadyRunningError, n as deleteReview$1, nn as lineageKeyFor, nt as queueForReview, on as mutateWorkspace, pn as summarise, pt as workspaceUrl, qn as resolveModel, qt as isClaimed, rr as detectAgents, sn as positionInLineage, st as ensureServer, tr as agentById, u as updateReview, un as recentWorkspaces, v as recordKissRun, vt as DEFAULT_WORKSPACE_PORT, w as record$1, wt as writePortPreference, x as runJob, xt as parseWorkspacePort, yt as MAX_WORKSPACE_PORT } from "./delete_review-B2bqhgtx.js";
|
|
3
3
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
import { existsSync, mkdirSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
|