prreviewbuddy 0.95.2 → 0.96.2
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 +69 -0
- package/dist/main.js +1 -1
- package/dist/{mcp_tools-DDbcXys2.js → mcp_tools-DmQKsAkC.js} +378 -11
- package/dist/server.js +10641 -8611
- package/package.json +1 -1
- package/static/chunk-C0JUspm9.js +1 -0
- package/static/{chunk-Cum6sHaC.js → chunk-CZ1YsjDz.js} +1 -1
- package/static/{chunk-D1LBeOJc.js → chunk-DLCpB0qV.js} +1 -1
- package/static/chunk-SPHga_7I.js +5 -0
- package/static/help.js +1 -1
- package/static/notifications.js +1 -1
- package/static/reviews.js +1 -1
- package/static/runs.js +1 -1
- package/static/settings.js +1 -1
- package/static/shell.css +292 -0
- package/static/workspace.js +85 -89
- package/static/chunk-CQIPqX7U.js +0 -1
- package/static/chunk-Cs33-tV3.js +0 -1
|
@@ -851,6 +851,12 @@ var CLAUDE_AGENT_ID = "claude";
|
|
|
851
851
|
* about sign-in to errors that have nothing to do with it, which is worse than staying quiet.
|
|
852
852
|
*/
|
|
853
853
|
var AUTHENTICATION$2 = /\b(?:auth(?:enticat\w*)?|oauth|credential|session expired|not logged in|log ?in)\b/i;
|
|
854
|
+
/**
|
|
855
|
+
* What Claude Code prints, and exits 1 on, for an argument it does not know: `error: unknown option
|
|
856
|
+
* '--setting-sources'` (seen on 2.1.318 with a made-up flag). Every argument comes from
|
|
857
|
+
* `claudeArgs`, so this means the installed CLI is older than one of them.
|
|
858
|
+
*/
|
|
859
|
+
var UNKNOWN_OPTION = /unknown option '--[\w-]+'/i;
|
|
854
860
|
/** Said to the caller that asked for the stop, and never shown to the reviewer who pressed it. */
|
|
855
861
|
var STOPPED$2 = "The agent was stopped.";
|
|
856
862
|
/** How long a stopped agent gets to end on its own terms before it is ended for it. */
|
|
@@ -1060,6 +1066,17 @@ var VERSION_TIMEOUT_MS$2 = 1e4;
|
|
|
1060
1066
|
* `--strict-mcp-config`, with no `--mcp-config` beside it, is the containment clause of the agent
|
|
1061
1067
|
* contract as Claude satisfies it. It tells the CLI to load no MCP servers at all, so `prb_review`
|
|
1062
1068
|
* is not there to be reached no matter which `CLAUDE_CONFIG_DIR` the spawn inherits.
|
|
1069
|
+
*
|
|
1070
|
+
* `--setting-sources user` is the other half. The run stands in a worktree of the change under
|
|
1071
|
+
* review, and by default the CLI also loads that worktree's `.claude/settings.json` and
|
|
1072
|
+
* `settings.local.json`: settings written by the change's author. Their hooks run as the reviewer,
|
|
1073
|
+
* before the model sees anything and outside `--allowedTools`, and their permissions widen the tool
|
|
1074
|
+
* policy. Observed on Claude Code 2.1.318 with a canary `SessionStart` hook: it ran under the
|
|
1075
|
+
* arguments above without this flag and not with it, while the reviewer's own hooks ran both times.
|
|
1076
|
+
* The same flag keeps out the worktree's `CLAUDE.md`, `.claude/agents` and `.claude/skills`, which
|
|
1077
|
+
* reach the model as instructions: a canary of each was absent from the `init` event and unknown to
|
|
1078
|
+
* the model under these arguments (2026-11-08), while the reviewer's own agents and skills remained.
|
|
1079
|
+
* `--bare` would also skip them, but it skips the reviewer's sign-in too.
|
|
1063
1080
|
*/
|
|
1064
1081
|
function claudeArgs(capability, requestedModel) {
|
|
1065
1082
|
return [
|
|
@@ -1070,6 +1087,8 @@ function claudeArgs(capability, requestedModel) {
|
|
|
1070
1087
|
"--allowedTools",
|
|
1071
1088
|
toolsFor(capability),
|
|
1072
1089
|
"--strict-mcp-config",
|
|
1090
|
+
"--setting-sources",
|
|
1091
|
+
"user",
|
|
1073
1092
|
...requestedModel ? ["--model", requestedModel] : []
|
|
1074
1093
|
];
|
|
1075
1094
|
}
|
|
@@ -1109,6 +1128,7 @@ var claudeAgent = {
|
|
|
1109
1128
|
* situation nobody is in until they have tried the obvious thing first.
|
|
1110
1129
|
*/
|
|
1111
1130
|
explainFailure(detail, env = {}) {
|
|
1131
|
+
if (UNKNOWN_OPTION.test(detail)) return "This Claude Code is too old for PR Review Buddy. Update it with `claude update`, then try again.";
|
|
1112
1132
|
if (!AUTHENTICATION$2.test(detail)) return null;
|
|
1113
1133
|
return `Claude Code could not sign in. This review was started using ${env.CLAUDE_CONFIG_DIR ? `the Claude profile at \`${env.CLAUDE_CONFIG_DIR}\`` : "your default Claude profile"}, so make sure that one is signed in.`;
|
|
1114
1134
|
},
|
|
@@ -1653,14 +1673,25 @@ var codexMcpRegistration = {
|
|
|
1653
1673
|
* `--no-mcp`. Two things were established by running read-only commands rather than by reading
|
|
1654
1674
|
* documentation, and they are the reason this adapter exists at all:
|
|
1655
1675
|
*
|
|
1656
|
-
* - MCP servers resolve
|
|
1657
|
-
* while leaving the reviewer's login working. So containment costs no credential.
|
|
1676
|
+
* - Configured MCP servers resolve from `$CODEX_HOME/config.toml`, which `--ignore-user-config`
|
|
1677
|
+
* drops while leaving the reviewer's login working. So containment costs no credential. Plugins
|
|
1678
|
+
* are the exception: a remote plugin's server (Notion, from the plugin cache) still started under
|
|
1679
|
+
* it on codex-cli 0.157.0, and only `--disable plugins` stopped it. Connected apps are the other:
|
|
1680
|
+
* the reviewer's ChatGPT apps arrive as a built-in `codex_apps` server (GitHub with
|
|
1681
|
+
* `merge_pull_request`, Google Drive with `share_file`, a web search) whose calls run on OpenAI's
|
|
1682
|
+
* side, where the read-only sandbox does not reach. `--disable apps` removes all of them.
|
|
1658
1683
|
* - A repository's own `.codex/config.toml` contributes nothing. This was the open question, and
|
|
1659
1684
|
* it mattered because a review runs in a worktree cut from the repository under review: a
|
|
1660
1685
|
* project-level server would have been an untrusted repository injecting a tool into a run that
|
|
1661
1686
|
* is supposed to have none. A canary server declared in project config does not appear in
|
|
1662
1687
|
* `codex mcp list` run from that repository.
|
|
1663
1688
|
*
|
|
1689
|
+
* The reviewed repository's `AGENTS.md` is kept out with `-c project_doc_max_bytes=0`, for the same
|
|
1690
|
+
* reason the Claude adapter keeps out `CLAUDE.md`: the author of the change can edit it in the same
|
|
1691
|
+
* change. `-c web_search="disabled"` removes the web tool, which a read-only review has no use for.
|
|
1692
|
+
* The repository's `.codex/` config and hooks need no flag: with user config ignored the repository
|
|
1693
|
+
* is untrusted, so its project layer is never loaded (verified 2026-11-08 with a canary repository).
|
|
1694
|
+
*
|
|
1664
1695
|
* `-c mcp_servers={}` is deliberately not used: the override layer is deep-merged, so an empty
|
|
1665
1696
|
* table iterates zero keys and leaves the configured servers exactly as they were. It looks like
|
|
1666
1697
|
* containment and is not.
|
|
@@ -1682,6 +1713,39 @@ var STOPPED = "The run was stopped.";
|
|
|
1682
1713
|
*/
|
|
1683
1714
|
var CARRIED = ["CODEX_HOME", "PATH"];
|
|
1684
1715
|
var VERSION_TIMEOUT_MS = 1e4;
|
|
1716
|
+
/**
|
|
1717
|
+
* The oldest Codex a review will start on.
|
|
1718
|
+
*
|
|
1719
|
+
* `-c web_search="disabled"` is what keeps a review off the web, and Codex ignores a `-c` key it
|
|
1720
|
+
* does not know (exit 0 on 0.157.0), so on a Codex older than the key the switch would do nothing
|
|
1721
|
+
* and say nothing. This is the oldest release where the switch is known to work: 0.122.0 is also
|
|
1722
|
+
* the first with `--ignore-user-config` and `--ignore-rules` (#18646), so nothing older could run
|
|
1723
|
+
* a review anyway. Established 2026-11-08 from the openai/codex source at each tag:
|
|
1724
|
+
*
|
|
1725
|
+
* - the hosted web tool is built only for `Cached` or `Live` (`create_web_search_tool` returns
|
|
1726
|
+
* `None` for `Disabled` at 0.122.0; `hosted_spec.rs` at 0.134.0), and
|
|
1727
|
+
* - the standalone web search extension, from 0.135.0, is enabled only when the mode is not
|
|
1728
|
+
* `Disabled` (checked at 0.135, 0.140, 0.145, 0.150 and 0.157).
|
|
1729
|
+
*
|
|
1730
|
+
* And on 0.157.0 by the tools the Responses API echoed back: `web.run` and `web__run` without the
|
|
1731
|
+
* switch, neither with it. Older macOS builds could not be run to repeat that: every release before
|
|
1732
|
+
* 0.132.0 is signed with a revoked certificate (0.131.0 is not notarized) and macOS refuses it.
|
|
1733
|
+
*/
|
|
1734
|
+
var MINIMUM_CODEX_VERSION = "0.122.0";
|
|
1735
|
+
/**
|
|
1736
|
+
* Why a review will not start on this Codex, or null when it will.
|
|
1737
|
+
*
|
|
1738
|
+
* An unreadable version is refused rather than assumed, and a pre-release of the minimum counts as
|
|
1739
|
+
* older than it: neither is known to have what the release has.
|
|
1740
|
+
*/
|
|
1741
|
+
function codexVersionRefusal(version) {
|
|
1742
|
+
const update = "Update it with `codex update`, or with the package manager you installed it from if that command is missing, then try again.";
|
|
1743
|
+
const parsed = version ? /^(\d+)\.(\d+)\.(\d+)(.*)$/.exec(version) : null;
|
|
1744
|
+
if (!parsed) return `PR Review Buddy could not tell which Codex is installed, and needs Codex ${MINIMUM_CODEX_VERSION} or newer. ${update}`;
|
|
1745
|
+
const have = parsed.slice(1, 4).map(Number);
|
|
1746
|
+
const need = MINIMUM_CODEX_VERSION.split(".").map(Number);
|
|
1747
|
+
return (have.map((part, i) => part - need[i]).find((diff) => diff !== 0) ?? (parsed[4].startsWith("-") ? -1 : 0)) < 0 ? `Codex ${version} is too old for PR Review Buddy, which needs Codex ${MINIMUM_CODEX_VERSION} or newer. ${update}` : null;
|
|
1748
|
+
}
|
|
1685
1749
|
var DEFAULT_TIMEOUT_MS = { "read-code": 12e5 };
|
|
1686
1750
|
function codexArgs(_capability, requestedModel) {
|
|
1687
1751
|
return [
|
|
@@ -1692,6 +1756,14 @@ function codexArgs(_capability, requestedModel) {
|
|
|
1692
1756
|
"read-only",
|
|
1693
1757
|
"--ignore-user-config",
|
|
1694
1758
|
"--ignore-rules",
|
|
1759
|
+
"--disable",
|
|
1760
|
+
"plugins",
|
|
1761
|
+
"--disable",
|
|
1762
|
+
"apps",
|
|
1763
|
+
"-c",
|
|
1764
|
+
"project_doc_max_bytes=0",
|
|
1765
|
+
"-c",
|
|
1766
|
+
"web_search=\"disabled\"",
|
|
1695
1767
|
"--ephemeral",
|
|
1696
1768
|
...requestedModel ? ["-m", requestedModel] : []
|
|
1697
1769
|
];
|
|
@@ -1822,6 +1894,14 @@ var AUTHENTICATION = /not (?:logged|signed) in|unauthori[sz]ed|401|authenticat|c
|
|
|
1822
1894
|
* authentication failure, so no Retry is offered.
|
|
1823
1895
|
*/
|
|
1824
1896
|
var REJECTED_KEY = /incorrect api key|invalid[_ ]api[_ ]key/i;
|
|
1897
|
+
/**
|
|
1898
|
+
* A Codex older than a switch a review passes: an unknown feature name (`Unknown feature flag:
|
|
1899
|
+
* <name>` on 0.157.0) or, older still, an option it has never heard of. Fixed by updating, so it is
|
|
1900
|
+
* said as that. `codex update` exists on 0.157.0, but the Codex reading this is older by definition
|
|
1901
|
+
* and may predate it, so the package manager is named as the fallback. Neither refusal reads as a sign-in failure, so it stays an execution failure and
|
|
1902
|
+
* Retry is there after the update.
|
|
1903
|
+
*/
|
|
1904
|
+
var TOO_OLD = /unknown feature flag|unexpected argument '--[\w-]+'/i;
|
|
1825
1905
|
function isAuthentication(detail) {
|
|
1826
1906
|
return AUTHENTICATION.test(detail) || REJECTED_KEY.test(detail);
|
|
1827
1907
|
}
|
|
@@ -1845,6 +1925,7 @@ var codexAgent = {
|
|
|
1845
1925
|
mcpRegistration: codexMcpRegistration,
|
|
1846
1926
|
/** Which tool, which profile, and the one command that changes it. See `agent.ts` for the budget. */
|
|
1847
1927
|
explainFailure(detail, env = {}) {
|
|
1928
|
+
if (TOO_OLD.test(detail)) return "This Codex is too old for PR Review Buddy. Update it with `codex update`, or with the package manager you installed it from if that command is missing, then try again.";
|
|
1848
1929
|
if (!isAuthentication(detail)) return null;
|
|
1849
1930
|
const profile = env.CODEX_HOME ? `the Codex profile at \`${env.CODEX_HOME}\`` : "your default Codex profile";
|
|
1850
1931
|
if (REJECTED_KEY.test(detail)) return `OpenAI refused a request from ${profile} even though Codex reports being signed in, which usually points to Codex's own setup rather than your sign-in. Run \`codex doctor\` to see what it is using, and check \`codex features list\` for an experimental feature to turn off with \`codex features disable <name>\`.`;
|
|
@@ -1864,11 +1945,20 @@ var codexAgent = {
|
|
|
1864
1945
|
encoding: "utf8",
|
|
1865
1946
|
env: spawnEnv(env)
|
|
1866
1947
|
}, (error, stdout) => {
|
|
1867
|
-
if (!error)
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1948
|
+
if (!error) {
|
|
1949
|
+
const version = versionFrom(stdout);
|
|
1950
|
+
const refusal = codexVersionRefusal(version);
|
|
1951
|
+
if (refusal) return resolvePromise({
|
|
1952
|
+
ok: false,
|
|
1953
|
+
found: true,
|
|
1954
|
+
reason: refusal
|
|
1955
|
+
});
|
|
1956
|
+
return resolvePromise({
|
|
1957
|
+
ok: true,
|
|
1958
|
+
version,
|
|
1959
|
+
path: whichOn("codex", spawnEnv(env).PATH)
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1872
1962
|
const found = error.code !== "ENOENT";
|
|
1873
1963
|
resolvePromise({
|
|
1874
1964
|
ok: false,
|
|
@@ -1881,6 +1971,9 @@ var codexAgent = {
|
|
|
1881
1971
|
async run(input) {
|
|
1882
1972
|
guardPrompt(input);
|
|
1883
1973
|
if (input.signal?.aborted) throw new AgentCancelledError(STOPPED);
|
|
1974
|
+
const installed = await codexAgent.available(input.env);
|
|
1975
|
+
if (!installed.ok) throw new CodexUnavailableError(installed.reason);
|
|
1976
|
+
if (input.signal?.aborted) throw new AgentCancelledError(STOPPED);
|
|
1884
1977
|
const reader = assemble$2(input.onProgress ?? (() => {}));
|
|
1885
1978
|
return new Promise((resolvePromise, reject) => {
|
|
1886
1979
|
const child = spawn("codex", codexArgs(input.capability, input.requestedModel), {
|
|
@@ -6556,6 +6649,159 @@ function lifecycleItemLabel(target, lifecycle, activity) {
|
|
|
6556
6649
|
return lifecycleAction(target);
|
|
6557
6650
|
}
|
|
6558
6651
|
//#endregion
|
|
6652
|
+
//#region ../../packages/review-harness/src/workspace/reviewer_outcome.ts
|
|
6653
|
+
var REVIEWER_OUTCOMES = [
|
|
6654
|
+
{
|
|
6655
|
+
id: "no-concerns",
|
|
6656
|
+
name: "Reviewed: no concerns",
|
|
6657
|
+
baseline: true
|
|
6658
|
+
},
|
|
6659
|
+
{
|
|
6660
|
+
id: "needs-changes",
|
|
6661
|
+
name: "Needs changes",
|
|
6662
|
+
baseline: true
|
|
6663
|
+
},
|
|
6664
|
+
{
|
|
6665
|
+
id: "needs-decision",
|
|
6666
|
+
name: "Needs a decision / answer",
|
|
6667
|
+
baseline: true
|
|
6668
|
+
},
|
|
6669
|
+
{
|
|
6670
|
+
id: "deeper-review",
|
|
6671
|
+
name: "Needs deeper review",
|
|
6672
|
+
baseline: false
|
|
6673
|
+
},
|
|
6674
|
+
{
|
|
6675
|
+
id: "later",
|
|
6676
|
+
name: "Later",
|
|
6677
|
+
baseline: false
|
|
6678
|
+
}
|
|
6679
|
+
];
|
|
6680
|
+
function parseOutcomeSnapshot(value) {
|
|
6681
|
+
if (typeof value !== "object" || value === null) return null;
|
|
6682
|
+
const raw = value;
|
|
6683
|
+
if (typeof raw.sessionId !== "string" || typeof raw.commitSha !== "string" || typeof raw.analysisRevision !== "string") return null;
|
|
6684
|
+
return {
|
|
6685
|
+
sessionId: raw.sessionId,
|
|
6686
|
+
commitSha: raw.commitSha,
|
|
6687
|
+
analysisRevision: raw.analysisRevision
|
|
6688
|
+
};
|
|
6689
|
+
}
|
|
6690
|
+
function outcomeDefinition(kind) {
|
|
6691
|
+
return REVIEWER_OUTCOMES.find((outcome) => outcome.id === kind);
|
|
6692
|
+
}
|
|
6693
|
+
function isBaselineKind(kind) {
|
|
6694
|
+
return outcomeDefinition(kind)?.baseline === true;
|
|
6695
|
+
}
|
|
6696
|
+
function parseStoredOutcome(value) {
|
|
6697
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
6698
|
+
const raw = value;
|
|
6699
|
+
if (typeof raw.kind !== "string" || !outcomeDefinition(raw.kind)) return void 0;
|
|
6700
|
+
if (typeof raw.at !== "number" || !Number.isFinite(raw.at)) return void 0;
|
|
6701
|
+
if (typeof raw.commitSha !== "string" || typeof raw.sessionId !== "string") return void 0;
|
|
6702
|
+
return {
|
|
6703
|
+
kind: raw.kind,
|
|
6704
|
+
at: raw.at,
|
|
6705
|
+
commitSha: raw.commitSha,
|
|
6706
|
+
sessionId: raw.sessionId,
|
|
6707
|
+
analysisRevision: typeof raw.analysisRevision === "string" ? raw.analysisRevision : "",
|
|
6708
|
+
...typeof raw.recordId === "string" ? { recordId: raw.recordId } : {},
|
|
6709
|
+
...typeof raw.analysisJobId === "string" ? { analysisJobId: raw.analysisJobId } : {},
|
|
6710
|
+
...Array.isArray(raw.answeredQuestions) ? { answeredQuestions: raw.answeredQuestions.filter((q) => typeof q === "string") } : {}
|
|
6711
|
+
};
|
|
6712
|
+
}
|
|
6713
|
+
function parseOutcomes(value) {
|
|
6714
|
+
if (!Array.isArray(value)) return [];
|
|
6715
|
+
return value.map(parseStoredOutcome).filter((outcome) => outcome !== void 0);
|
|
6716
|
+
}
|
|
6717
|
+
/** Newest last, capped. The baseline lives in its own field, so nothing here has to survive pruning. */
|
|
6718
|
+
function appendOutcome(history, outcome) {
|
|
6719
|
+
return [...history, outcome].slice(-20);
|
|
6720
|
+
}
|
|
6721
|
+
/** The newer of two baselines; a non-baseline outcome never replaces one. */
|
|
6722
|
+
function nextBaseline(current, outcome) {
|
|
6723
|
+
if (!isBaselineKind(outcome.kind)) return current;
|
|
6724
|
+
return !current || outcome.at >= current.at ? outcome : current;
|
|
6725
|
+
}
|
|
6726
|
+
/** The newest baseline across a lineage's records, so a re-analysis that made a new record keeps it. */
|
|
6727
|
+
function lastBaseline(baselines) {
|
|
6728
|
+
let newest;
|
|
6729
|
+
for (const baseline of baselines) if (baseline && (!newest || baseline.at > newest.at)) newest = baseline;
|
|
6730
|
+
return newest;
|
|
6731
|
+
}
|
|
6732
|
+
function sameSha(a, b) {
|
|
6733
|
+
if (!a || !b) return false;
|
|
6734
|
+
const length = Math.min(a.length, b.length);
|
|
6735
|
+
return length >= 7 && a.slice(0, length) === b.slice(0, length);
|
|
6736
|
+
}
|
|
6737
|
+
/** How an outcome relates to the analysis on screen. */
|
|
6738
|
+
function outcomeStanding(outcome, snapshot) {
|
|
6739
|
+
if (!sameSha(outcome.commitSha, snapshot.commitSha)) return "earlier-snapshot";
|
|
6740
|
+
if (outcome.sessionId !== snapshot.sessionId || outcome.analysisRevision !== snapshot.analysisRevision) return "earlier-analysis";
|
|
6741
|
+
return "current";
|
|
6742
|
+
}
|
|
6743
|
+
/**
|
|
6744
|
+
* Verdicts come from the history and from the stored baseline, which is never pruned: twenty
|
|
6745
|
+
* deferrals after a verdict push it out of the history, but not out of the reading.
|
|
6746
|
+
*/
|
|
6747
|
+
function readOutcomes(history, snapshot, baseline) {
|
|
6748
|
+
const verdicts = history.filter((outcome) => isBaselineKind(outcome.kind));
|
|
6749
|
+
if (baseline && isBaselineKind(baseline.kind) && !verdicts.some((outcome) => outcome.at === baseline.at && outcome.kind === baseline.kind)) verdicts.push(baseline);
|
|
6750
|
+
verdicts.sort((a, b) => a.at - b.at);
|
|
6751
|
+
const newestVerdict = verdicts.at(-1);
|
|
6752
|
+
if (!snapshot) return newestVerdict ? { previous: {
|
|
6753
|
+
outcome: newestVerdict,
|
|
6754
|
+
standing: "earlier-snapshot"
|
|
6755
|
+
} } : {};
|
|
6756
|
+
const verdict = verdicts.filter((outcome) => outcomeStanding(outcome, snapshot) === "current").at(-1);
|
|
6757
|
+
const deferral = history.filter((outcome) => !isBaselineKind(outcome.kind) && outcomeStanding(outcome, snapshot) === "current" && (!verdict || outcome.at > verdict.at)).at(-1);
|
|
6758
|
+
const reading = {};
|
|
6759
|
+
if (verdict) reading.verdict = verdict;
|
|
6760
|
+
else if (newestVerdict) reading.previous = {
|
|
6761
|
+
outcome: newestVerdict,
|
|
6762
|
+
standing: outcomeStanding(newestVerdict, snapshot)
|
|
6763
|
+
};
|
|
6764
|
+
if (deferral) reading.deferral = deferral;
|
|
6765
|
+
return reading;
|
|
6766
|
+
}
|
|
6767
|
+
/** "Needs changes", or how an older one reads beside a newer analysis. */
|
|
6768
|
+
function outcomeLabel(outcome, standing) {
|
|
6769
|
+
const name = outcomeDefinition(outcome.kind)?.name ?? outcome.kind;
|
|
6770
|
+
if (standing === "current") return name;
|
|
6771
|
+
if (standing === "earlier-analysis") return `Outcome on an earlier analysis: ${name}`;
|
|
6772
|
+
return `${name} on ${outcome.commitSha.slice(0, 7)} (earlier snapshot)`;
|
|
6773
|
+
}
|
|
6774
|
+
//#endregion
|
|
6775
|
+
//#region ../../packages/review-harness/src/workspace/analysis_revision.ts
|
|
6776
|
+
/**
|
|
6777
|
+
* A short fingerprint of the analysis a reviewer is reading: its findings, statuses, notes and text.
|
|
6778
|
+
*
|
|
6779
|
+
* The session id and the commit do not move when a Re-check reconciles findings at the same commit,
|
|
6780
|
+
* so a verdict checked against those two alone would be accepted for findings the reviewer never saw.
|
|
6781
|
+
* The reviewer's own ticks are not part of it: those are the reviewer's acts, not the analysis.
|
|
6782
|
+
*/
|
|
6783
|
+
function analysisRevision(workspace) {
|
|
6784
|
+
const result = workspace.session.result;
|
|
6785
|
+
if (!result) return "";
|
|
6786
|
+
return createHash("sha256").update(JSON.stringify(result)).digest("hex").slice(0, 16);
|
|
6787
|
+
}
|
|
6788
|
+
/** The analysed commit, falling back the way `reviewedCommit` does. */
|
|
6789
|
+
function analysedCommit(workspace) {
|
|
6790
|
+
return workspace.session.metadata?.commitSha || workspace.changeSet.data.pullRequest.head.sha || "";
|
|
6791
|
+
}
|
|
6792
|
+
function analysisSnapshot(workspace) {
|
|
6793
|
+
if (!workspace.session.result) return null;
|
|
6794
|
+
return {
|
|
6795
|
+
sessionId: workspace.session.id,
|
|
6796
|
+
commitSha: analysedCommit(workspace),
|
|
6797
|
+
analysisRevision: analysisRevision(workspace)
|
|
6798
|
+
};
|
|
6799
|
+
}
|
|
6800
|
+
/** When the analysis on screen was last produced or reconciled: the upper time bound for its history. */
|
|
6801
|
+
function analysedAt(workspace) {
|
|
6802
|
+
return Math.max(workspace.session.metadata?.generatedAt ?? 0, workspace.lastUpdate?.at ?? 0, workspace.lastRecheck?.at ?? 0, workspace.createdAt);
|
|
6803
|
+
}
|
|
6804
|
+
//#endregion
|
|
6559
6805
|
//#region ../../packages/review-harness/src/workspace/store.ts
|
|
6560
6806
|
var WORKSPACES_DIR = join(STORE_ROOT, "workspaces");
|
|
6561
6807
|
/**
|
|
@@ -6750,6 +6996,18 @@ function workspaceRevision(workspace) {
|
|
|
6750
6996
|
}
|
|
6751
6997
|
}
|
|
6752
6998
|
/**
|
|
6999
|
+
* Moves whenever any review is written or deleted, by this process or another: every write stages a
|
|
7000
|
+
* file in the directory and renames it into place, and a delete unlinks one. One stat, where
|
|
7001
|
+
* `workspaceStamps` is one per review, for a cache to ask whether anything changed at all.
|
|
7002
|
+
*/
|
|
7003
|
+
function workspacesRevision() {
|
|
7004
|
+
try {
|
|
7005
|
+
return statSync(WORKSPACES_DIR).mtimeMs;
|
|
7006
|
+
} catch {
|
|
7007
|
+
return 0;
|
|
7008
|
+
}
|
|
7009
|
+
}
|
|
7010
|
+
/**
|
|
6753
7011
|
* Every stored review as an id and a revision token, without reading one of them.
|
|
6754
7012
|
*
|
|
6755
7013
|
* Here rather than in `reviews_revision.ts` because this module owns the layout of `workspaces/`,
|
|
@@ -7031,6 +7289,14 @@ function groupByLineage(reviews) {
|
|
|
7031
7289
|
}
|
|
7032
7290
|
return [...groups].map(([key, members]) => {
|
|
7033
7291
|
const history = [...members].sort((a, b) => b.createdAt - a.createdAt);
|
|
7292
|
+
const [newest] = history;
|
|
7293
|
+
if (newest.outcomeInputs && history.length > 1) {
|
|
7294
|
+
const { outcome: _outcome, deferral: _deferral, ...rest } = newest;
|
|
7295
|
+
history[0] = {
|
|
7296
|
+
...rest,
|
|
7297
|
+
...lineageOutcome(newest.outcomeInputs, history.map((review) => review.outcomeInputs))
|
|
7298
|
+
};
|
|
7299
|
+
}
|
|
7034
7300
|
return {
|
|
7035
7301
|
key,
|
|
7036
7302
|
latest: history[0],
|
|
@@ -7161,6 +7427,8 @@ function summarise(workspace) {
|
|
|
7161
7427
|
})(),
|
|
7162
7428
|
lastViewedAt: workspace.lastViewedAt,
|
|
7163
7429
|
completedAt: workspace.completedAt,
|
|
7430
|
+
outcomeInputs: outcomeInputsOf(workspace),
|
|
7431
|
+
...outcomeSummary(outcomeInputsOf(workspace)),
|
|
7164
7432
|
pinned: workspace.pinned === true,
|
|
7165
7433
|
lifecycle: parseLifecycle(workspace.lifecycle),
|
|
7166
7434
|
paths: files.slice(0, MAX_INDEXED_PATHS).map((file) => file.name),
|
|
@@ -7171,6 +7439,42 @@ function summarise(workspace) {
|
|
|
7171
7439
|
authorName: workspace.prContext?.pullRequest.author?.name || void 0
|
|
7172
7440
|
};
|
|
7173
7441
|
}
|
|
7442
|
+
function outcomeInputsOf(workspace) {
|
|
7443
|
+
const baseline = parseStoredOutcome(workspace.reviewedBaseline);
|
|
7444
|
+
return {
|
|
7445
|
+
history: parseOutcomes(workspace.reviewerOutcomes),
|
|
7446
|
+
snapshot: analysisSnapshot(workspace),
|
|
7447
|
+
...baseline ? { baseline } : {}
|
|
7448
|
+
};
|
|
7449
|
+
}
|
|
7450
|
+
/**
|
|
7451
|
+
* The newest record's outcomes read with the newest baseline of its whole lineage, as Quick Review
|
|
7452
|
+
* reads them. A fresh analysis makes a record with no baseline of its own, and the verdict on the
|
|
7453
|
+
* one before must still show on the card, as an earlier one, instead of vanishing from it.
|
|
7454
|
+
*/
|
|
7455
|
+
function lineageOutcome(newest, lineage) {
|
|
7456
|
+
return outcomeSummary(newest, lastBaseline([newest.baseline, ...lineage.map((inputs) => inputs?.baseline)]));
|
|
7457
|
+
}
|
|
7458
|
+
function outcomeSummary(inputs, baseline = inputs.baseline) {
|
|
7459
|
+
const reading = readOutcomes(inputs.history, inputs.snapshot, baseline);
|
|
7460
|
+
const verdict = reading.verdict ? {
|
|
7461
|
+
outcome: reading.verdict,
|
|
7462
|
+
standing: "current"
|
|
7463
|
+
} : reading.previous;
|
|
7464
|
+
return {
|
|
7465
|
+
...verdict ? { outcome: {
|
|
7466
|
+
kind: verdict.outcome.kind,
|
|
7467
|
+
standing: verdict.standing,
|
|
7468
|
+
label: outcomeLabel(verdict.outcome, verdict.standing),
|
|
7469
|
+
at: verdict.outcome.at
|
|
7470
|
+
} } : {},
|
|
7471
|
+
...reading.deferral ? { deferral: {
|
|
7472
|
+
kind: reading.deferral.kind,
|
|
7473
|
+
label: outcomeLabel(reading.deferral, "current"),
|
|
7474
|
+
at: reading.deferral.at
|
|
7475
|
+
} } : {}
|
|
7476
|
+
};
|
|
7477
|
+
}
|
|
7174
7478
|
/**
|
|
7175
7479
|
* The commit a review was made from, however the record spells it. Null when it recorded none.
|
|
7176
7480
|
*
|
|
@@ -8386,6 +8690,14 @@ function sanitisePullRequest(value) {
|
|
|
8386
8690
|
if (template) pr.fileUrlTemplate = template;
|
|
8387
8691
|
const author = pullRequestAuthor(value.author);
|
|
8388
8692
|
if (author) pr.author = author;
|
|
8693
|
+
if (isRecord$1(value.headRepository)) {
|
|
8694
|
+
const owner = label(value.headRepository.owner);
|
|
8695
|
+
const repo = label(value.headRepository.repo);
|
|
8696
|
+
if (owner && repo) pr.headRepository = {
|
|
8697
|
+
owner,
|
|
8698
|
+
repo
|
|
8699
|
+
};
|
|
8700
|
+
}
|
|
8389
8701
|
return pr;
|
|
8390
8702
|
}
|
|
8391
8703
|
/**
|
|
@@ -9093,7 +9405,7 @@ function slug(repo) {
|
|
|
9093
9405
|
* `baseRefOid` is a diff taken against the wrong *commit*, which is worse because the branch name
|
|
9094
9406
|
* is right and everything reads as normal. A list nobody asserts is a list somebody tidies.
|
|
9095
9407
|
*/
|
|
9096
|
-
var PR_FIELDS = "number,url,title,body,headRefName,headRefOid,baseRefName,baseRefOid,changedFiles,author";
|
|
9408
|
+
var PR_FIELDS = "number,url,title,body,headRefName,headRefOid,baseRefName,baseRefOid,changedFiles,author,headRepository,headRepositoryOwner";
|
|
9097
9409
|
async function viewRequest(repo, reference, cwd, env) {
|
|
9098
9410
|
return await ghJson([
|
|
9099
9411
|
"pr",
|
|
@@ -9142,7 +9454,11 @@ function identity(raw) {
|
|
|
9142
9454
|
author: author ? {
|
|
9143
9455
|
name: text$2(author.name),
|
|
9144
9456
|
handle: text$2(author.login)
|
|
9145
|
-
} : null
|
|
9457
|
+
} : null,
|
|
9458
|
+
...text$2(raw.headRepositoryOwner?.login) && text$2(raw.headRepository?.name) ? { headRepository: {
|
|
9459
|
+
owner: text$2(raw.headRepositoryOwner?.login),
|
|
9460
|
+
repo: text$2(raw.headRepository?.name)
|
|
9461
|
+
} } : {}
|
|
9146
9462
|
};
|
|
9147
9463
|
}
|
|
9148
9464
|
/**
|
|
@@ -11337,6 +11653,32 @@ ${diff}`;
|
|
|
11337
11653
|
};
|
|
11338
11654
|
}
|
|
11339
11655
|
//#endregion
|
|
11656
|
+
//#region ../../packages/review-harness/src/domain/ai_review/behaviour_change.ts
|
|
11657
|
+
/** Both sides present and non-empty, or nothing: half an explanation is not one. */
|
|
11658
|
+
function parseBehaviourChange(value) {
|
|
11659
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
11660
|
+
const raw = value;
|
|
11661
|
+
const before = typeof raw.before === "string" ? raw.before.trim() : "";
|
|
11662
|
+
const after = typeof raw.after === "string" ? raw.after.trim() : "";
|
|
11663
|
+
return before && after ? {
|
|
11664
|
+
before,
|
|
11665
|
+
after
|
|
11666
|
+
} : void 0;
|
|
11667
|
+
}
|
|
11668
|
+
function withBehaviourChange(change) {
|
|
11669
|
+
return change ? { behaviourChange: change } : {};
|
|
11670
|
+
}
|
|
11671
|
+
/**
|
|
11672
|
+
* The behaviourChange an update keeps.
|
|
11673
|
+
*
|
|
11674
|
+
* Restated: the new one. New code and none restated: dropped, because the old one describes a head
|
|
11675
|
+
* that no longer exists. A conversation-only pass read no new code, so the previous one still holds.
|
|
11676
|
+
*/
|
|
11677
|
+
function mergeBehaviourChange(previous, restated, codeChanged) {
|
|
11678
|
+
if (restated) return restated;
|
|
11679
|
+
return codeChanged ? void 0 : previous;
|
|
11680
|
+
}
|
|
11681
|
+
//#endregion
|
|
11340
11682
|
//#region ../../packages/review-harness/src/domain/utils/html.ts
|
|
11341
11683
|
function escapeHtml(value) {
|
|
11342
11684
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -12652,6 +12994,10 @@ var CONSEQUENCE_GUIDANCE = `"consequence" on an issue: the same issue told as wh
|
|
|
12652
12994
|
- An issue names a concrete consequence. A request for missing requirements with no concrete consequence is a question for the author, not an issue, and is never raised as both.`;
|
|
12653
12995
|
/** What "orientation" is for, as the first analysis and an update ask for it. */
|
|
12654
12996
|
var ORIENTATION_GUIDANCE = `"orientation": one sentence, at most about 25 words, naming the behaviour or product area this change touches, for a reviewer about to decide on its issues (e.g. "This change affects receipt extraction and how duplicate receipt details are combined."). Say what it touches, not whether it is safe, and do not summarise the issues.`;
|
|
12997
|
+
/** The behaviourChange shape, written out whole for every prompt that asks for it. */
|
|
12998
|
+
var BEHAVIOUR_CHANGE_FIELD = "\"behaviourChange\"?: { \"before\": string, \"after\": string }";
|
|
12999
|
+
/** What "behaviourChange" is for, as the first analysis and an update ask for it. */
|
|
13000
|
+
var BEHAVIOUR_CHANGE_GUIDANCE = `"behaviourChange": how the behaviour this pull request touches works today and how it works with the whole pull request applied, for a reviewer who knows the product. "before" is the behaviour at the base the pull request targets; "after" is the behaviour at the head you analysed. One to three plain sentences each, leading with what a user or the system experiences, not with the implementation (e.g. before: "A failed payment is shown as an error and the order is cancelled.", after: "A failed payment is retried twice before the order is cancelled, and the customer is emailed after the last attempt."). Describe the whole pull request, not one commit. Omit the field entirely when the code you read does not establish how it works today: never guess "before".`;
|
|
12655
13001
|
/**
|
|
12656
13002
|
* How an update keeps a prior finding's consequence and resolution true, beside its status.
|
|
12657
13003
|
*
|
|
@@ -13458,6 +13804,7 @@ var STRUCTURED_OUTPUT_INSTRUCTIONS = `In addition to the review principles above
|
|
|
13458
13804
|
},
|
|
13459
13805
|
"overallRisk": "low" | "medium" | "high",
|
|
13460
13806
|
"orientation": string,
|
|
13807
|
+
${BEHAVIOUR_CHANGE_FIELD},
|
|
13461
13808
|
"sections": [{
|
|
13462
13809
|
"id": string,
|
|
13463
13810
|
"order": number,
|
|
@@ -13495,6 +13842,7 @@ Field guidance:
|
|
|
13495
13842
|
- ${CONSEQUENCE_GUIDANCE}
|
|
13496
13843
|
- ${QUESTION_FRAMING_GUIDANCE}
|
|
13497
13844
|
- ${ORIENTATION_GUIDANCE}
|
|
13845
|
+
- ${BEHAVIOUR_CHANGE_GUIDANCE}
|
|
13498
13846
|
- "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.
|
|
13499
13847
|
- "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.
|
|
13500
13848
|
- "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.
|
|
@@ -13553,6 +13901,7 @@ function parseUnchecked(raw) {
|
|
|
13553
13901
|
summary,
|
|
13554
13902
|
overallRisk: RISKS.includes(obj.overallRisk) ? obj.overallRisk : "medium",
|
|
13555
13903
|
...asString$1(obj.orientation).trim() ? { orientation: asString$1(obj.orientation).trim() } : {},
|
|
13904
|
+
...withBehaviourChange(parseBehaviourChange(obj.behaviourChange)),
|
|
13556
13905
|
sections: normalizeSections(obj.sections),
|
|
13557
13906
|
issues: normalizeIssues(obj.issues),
|
|
13558
13907
|
questions: normalizeQuestions(obj.questions),
|
|
@@ -18226,6 +18575,7 @@ var RECONCILIATION_SHAPE = `{
|
|
|
18226
18575
|
}],
|
|
18227
18576
|
"summary": string,
|
|
18228
18577
|
"orientation": string,
|
|
18578
|
+
${BEHAVIOUR_CHANGE_FIELD},
|
|
18229
18579
|
"overallRisk": "low" | "medium" | "high",
|
|
18230
18580
|
"recommendation": string,
|
|
18231
18581
|
"nextSteps": string[]
|
|
@@ -18333,6 +18683,12 @@ var GRADE_TASK = {
|
|
|
18333
18683
|
* written to fix a finding is precisely where a regression gets introduced. A run that only
|
|
18334
18684
|
* reassessed the list has done half the work, and it is the half that looks complete.
|
|
18335
18685
|
*/
|
|
18686
|
+
/** The earlier before-and-after, quoted so the model can restate it rather than start over. */
|
|
18687
|
+
function previousBehaviourChange(previous) {
|
|
18688
|
+
const change = previous.behaviourChange;
|
|
18689
|
+
if (!change) return "";
|
|
18690
|
+
return ` (the earlier analysis said: before: ${JSON.stringify(change.before)}; after: ${JSON.stringify(change.after)}. Keep what still holds, correct what the new changes alter)`;
|
|
18691
|
+
}
|
|
18336
18692
|
function buildReconciliationInstructions(input, options = {}) {
|
|
18337
18693
|
const { previous, totalCommits, changedFiles, possiblyTruncated } = input;
|
|
18338
18694
|
const reassess = input.reassess ?? /* @__PURE__ */ new Map();
|
|
@@ -18366,7 +18722,7 @@ ${REASSESS_TASK}` : ""}
|
|
|
18366
18722
|
${GRADE_TASK.question}`] : [],
|
|
18367
18723
|
conversationOnly ? `No commits were pushed, so there is no new code to review. Report a problem in "newIssues" only where the conversation points at something in the code the earlier analysis missed and you have confirmed it by reading the code. Do NOT repeat a previous finding here: if it still stands, that is what "still-open" is for.` : `Read the changed code above as a reviewer, not only as an auditor of the numbered list. These commits have not been reviewed by anyone. Report every genuinely NEW problem you find in them in "newIssues", whether or not it relates to anything on the list — code written to fix a finding is exactly where a regression gets introduced, and reassessing the list and stopping there is half the job. Do NOT repeat a previous finding here: if it still stands, that is what "still-open" is for.`,
|
|
18368
18724
|
`If the new changes introduce an area the earlier analysis had no section for, add it to "newSections". Otherwise return an empty array. Do not restate or restructure the earlier analysis's existing sections.`,
|
|
18369
|
-
`Give a fresh "summary", "overallRisk", "recommendation" and "nextSteps" for the pull request AS IT NOW STANDS, taking both the earlier findings that survive and the new changes into account. Unlike a partial review, you have been shown the earlier analysis in full and are expected to revise its verdict where the new changes justify it. ${SUMMARY_GUIDANCE} ${ORIENTATION_GUIDANCE}
|
|
18725
|
+
`Give a fresh "summary", "overallRisk", "recommendation" and "nextSteps" for the pull request AS IT NOW STANDS, taking both the earlier findings that survive and the new changes into account. Unlike a partial review, you have been shown the earlier analysis in full and are expected to revise its verdict where the new changes justify it. ${SUMMARY_GUIDANCE} ${ORIENTATION_GUIDANCE} ${BEHAVIOUR_CHANGE_GUIDANCE} Restate "behaviourChange" for the whole pull request as it now stands${previousBehaviourChange(previous)}, or omit it when the evidence does not establish it.`
|
|
18370
18726
|
].map((task, index) => `${index + 1}. ${task}`).join("\n");
|
|
18371
18727
|
return `${conversationOnly ? `You are RECONCILING an earlier analysis of this pull request against what has changed in its conversation since that analysis was made.
|
|
18372
18728
|
|
|
@@ -18540,6 +18896,7 @@ function parseReconciliationResult(raw, priorCount, options = {}) {
|
|
|
18540
18896
|
})),
|
|
18541
18897
|
summary: asString$1(obj.summary).trim(),
|
|
18542
18898
|
orientation: asString$1(obj.orientation).trim(),
|
|
18899
|
+
...withBehaviourChange(parseBehaviourChange(obj.behaviourChange)),
|
|
18543
18900
|
overallRisk: RISKS.includes(obj.overallRisk) ? obj.overallRisk : null,
|
|
18544
18901
|
recommendation: asString$1(obj.recommendation).trim(),
|
|
18545
18902
|
nextSteps: asStringArray(obj.nextSteps)
|
|
@@ -18817,6 +19174,15 @@ function findingsChanged(counts) {
|
|
|
18817
19174
|
}
|
|
18818
19175
|
//#endregion
|
|
18819
19176
|
//#region ../../packages/review-harness/src/domain/ai_review/update_reconciliation.ts
|
|
19177
|
+
/**
|
|
19178
|
+
* Folding an update's answer onto the analysis it brings up to date.
|
|
19179
|
+
*
|
|
19180
|
+
* One function for every client that updates a review, so the workspace and the extension cannot
|
|
19181
|
+
* come to read the same evidence differently: what a cited comment may settle, when a resolution is
|
|
19182
|
+
* marked for reassessment instead, which findings a changed comment reopens, what each status is
|
|
19183
|
+
* recorded against, and which threads a new finding may claim. A client differs only in how it
|
|
19184
|
+
* fetched the changes and ran the model.
|
|
19185
|
+
*/
|
|
18820
19186
|
function reconciledAgainstOf(basis) {
|
|
18821
19187
|
return basis.code ? basis.discussion ? "commits-and-conversation" : "commits" : "conversation";
|
|
18822
19188
|
}
|
|
@@ -18849,6 +19215,7 @@ function applyReconciliation(previous, parsed, context) {
|
|
|
18849
19215
|
schemaVersion: 1,
|
|
18850
19216
|
summary: parsed.summary || previous.summary,
|
|
18851
19217
|
...parsed.orientation || previous.orientation ? { orientation: parsed.orientation || previous.orientation } : {},
|
|
19218
|
+
...withBehaviourChange(mergeBehaviourChange(previous.behaviourChange, parsed.behaviourChange, context.codeChanged)),
|
|
18852
19219
|
...motivation ? { motivation } : {},
|
|
18853
19220
|
overallRisk: parsed.overallRisk ?? previous.overallRisk,
|
|
18854
19221
|
recommendation: parsed.recommendation || previous.recommendation,
|
|
@@ -22175,4 +22542,4 @@ var MCP_TOOL_NAMES = [
|
|
|
22175
22542
|
"ask_buddy"
|
|
22176
22543
|
];
|
|
22177
22544
|
//#endregion
|
|
22178
|
-
export {
|
|
22545
|
+
export { statusLabel as $, gradeScale as $a, workspacesRevision as $i, isArchitectureId as $n, setMembership as $o, describeSubject as $r, resolveWorkspacePort as $t, PR_FILTER_OPTIONS as A, doneVerb as Aa, indexKeptIds as Ai, telemetryUploadEnabled as An, readAgentPreference as Ao, EXPLAIN_SIMPLY_PROMPT as Ar, interruption as At, completedFold as B, explanationActionOf as Ba, matchingWorkspaceIds as Bi, ARCHITECTURE_DIR as Bn, runnableAgents as Bo, renderInlineText as Br, healthyPort as Bt, parsePullRequestLink as C, parseLifecycle as Ca, clearClaim as Ci, append as Cn, shorthandOf as Co, existingPaths as Cr, findingRef as Ct, INDEX_SECTIONS as D, queueRemove as Da, decideKissRecommendation as Di, deliver$1 as Dn, resolveModel as Do, storedConversation as Dr, reviewTagsIn as Dt, relativeTime as E, queueAddMany as Ea, CLAIM_LEASE_MS as Ei, stamp as En, resolveAgent as Eo, saveRefresh as Er, reviewTag as Et, collectionEmptyText as F, resolvedByConversation as Fa, lineageKey as Fi, mapRuntimes as Fn, agentFor as Fo, GITHUB_MARKDOWN as Fr, recordAskAccepted as Ft, isFoldKey as G, NO_STATUS as Ga, previousKissResult as Gi, repositoryKey as Gn, cleanName as Go, MANAGED_ROOT as Gr, stopChild as Gt, countLabel as H, isExplanationAction as Ha, outcomeInputsOf as Hi, loadArchitecture as Hn, AGENT_RUN_MARKER as Ho, ReviewBeingDeletedError as Hr, reviewsUrl as Ht, collectionFold as I, reviewerDispositions as Ia, lineageKeyFor as Ii, recordRuntimeBestEffort as In, agentIdentityOf as Io, renderMarkdown as Ir, readIndexToken as It, pinItemLabel as J, closesFinding as Ja, reviewedRepositories as Ji, trackedFiles as Jn, createWithMembers as Jo, isWorktreeHeld as Jr, writeServerRecord as Jt, markItemLabel as K, analysisRefOf as Ka, recentWorkspaces as Ki, storeArchitecture as Kn, collectionsStamp as Ko, createWorktree as Kr, stopServer as Kt, collectionHash as L, verdictClosure as La, lineageOutcome as Li, RUNTIME_WORDS as Ln, detectAgents as Lo, CODE_BLOCK_CLASS as Lr, bootstrapUrl as Lt, authorOptionLabel as M, isQuestionOutstanding as Ma, kissDecisionRefusal as Mi, PACKAGE_NAME as Mn, CONFIG_PATH as Mo, UNESTABLISHED_MOTIVATION as Mr, isTerminalAsk as Mt, authorOptions as N, issuesOutstanding as Na, kissDecisionsOf as Ni, feedbackUrl as Nn, AGENT_IDS as No, authorPreview as Nr, markAskRunning as Nt, INDEX_SECTION_NAMES as O, queueStamp as Oa, followedRefName as Oi, recordTelemetryUploadConsent as On, agentIdOf as Oo, isUnchanged as Or, endAskRequest as Ot, collectionCountLabel as P, questionsOutstanding as Pa, latestKissRun as Pi, waitingHistory as Pn, agentById as Po, hasAuthorContent as Pr, readAskRequest as Pt, selectAllLabel as Q, findingStatus as Qa, workspaceStamps as Qi, ancestorsOf as Qn, renameCollection as Qo, describeMovement as Qr, parseWorkspacePort as Qt, collectionPlacement as R, processDiscussion as Ra, lineagePosition as Ri, describeRuntimes as Rn, isAgentId as Ro, CODE_COPY_CLASS as Rr, clearServerRecordOwnedBy as Rt, extractPullRequestLinks as S, offersLifecycle as Sa, purposeOf as Si, startJob as Sn, shortNameOf as So, withUsageRecorded as Sr, findingAtRef as St, runBatch as T, queueAdd as Ta, isClaimed as Ti, record as Tn, refExists as To, prepareConversationWithin as Tr, refOf as Tt, homeSection as U, withExplanation as Ua, positionInLineage as Ui, partFiles as Un, AgentCancelledError as Uo, isBusy as Ur, serverEntryExists as Ut, completedLabel as V, explanationOf as Va, mutateWorkspace as Vi, checkArchitecture as Vn, versionFrom as Vo, modelHelpLines as Vr, readServerRecord as Vt, isCollectionId as W, ANALYSIS_ONLY as Wa, presentedKissRunOf as Wi, readArchitecture as Wn, AgentUnavailableError as Wo, withReviewLock as Wr, spawnServer as Wt, queueOptionLabel as X, findingAssessment as Xa, touchWorkspace as Xi, QUOTE_LIMITS as Xn, nameProblemText as Xo, removeWorktree as Xr, MAX_WORKSPACE_PORT as Xt, queueItemLabel as Y, currentGradeIn as Ya, summarise as Yi, ownersOf as Yn, deleteCollection as Yo, readMarker as Yr, DEFAULT_WORKSPACE_PORT as Yt, sectionOpen as Z, findingIn as Za, workspaceRevision as Zi, REPOSITORY_SCOPE as Zn, readCollections as Zo, requestJobStop as Zr, MIN_WORKSPACE_PORT as Zt, endRun as _, lifecycleChip as _a, PHASES as _i, observeReview as _n, DetachedHeadError as _o, removeNotification as _r, applyUpdate as _t, lineageIds as a, nextBaseline as aa, fetchHead as ai, cancelUpdate as an, statusLookup as ao, createArchitectureNotification as ar, retryableMiss as at, stopRun as b, lifecyclePillAction as ba, phasesFor as bi, reanalyseReview as bn, resolveBase as bo, unreadAround as br, agentDisplayName as bt, discardJob as c, outcomeStanding as ca, RUN_RECORD_RETENTION_MS as ci, parkedUpdateRefusal as cn, SEVERITIES as co, eventName as cr, activateQueued as ct, startKissJob as d, readOutcomes as da, endedAt as di, askCheckout as dn, objectWithAnyOf as do, markRead as dr, settleQueue as dt, analysedAt as ea, findExistingReview as ei, statedWorkspacePort as en, isGradeFor as eo, writeJsonAtomically as er, withWriteLock as es, statusPillLabel as et, isDriving as f, sameSha as fa, fail as fi, findingsChanged as fn, describeAuthorship as fo, markReadForReview as fr, RUNNER_KINDS as ft, completeRun as g, isLifecycle as ga, saveJob as gi, failureOf as gn, originRemote as go, readStateOf as gr, spawnRunner as gt, beginRun as h, activityLabel as ha, pruneJobResidue as hi, runningJobs as hn, resolveTarget as ho, readNotification as hr, runDetached as ht, deleteReview as i, lastBaseline as ia, reviewedShaOf as ii, RefreshUnavailableError as in, sourcePhrase as io, categoryOf as ir, openDestination as it, anythingRunning as j, isIssueOutstanding as ja, indexReviewGroups as ji, BUILD_VERSION as jn, writeAgentPreference as jo, chatConversationSection as jr, isRequestId as jt, LIST_SECTIONS as k, readQueue as ka, groupByLineage as ki, telemetryUploadConsent as kn, clearAgentPreference as ko, agentEnvOf as kr, forgetAskRequest as kt, kissAgentId as l, parseOutcomeSnapshot as la, allJobs as li, recheckConversation as ln, asString$1 as lo, listNotifications as lr, compactQueue as lt, RUN_KINDS as m, activityAttributes as ma, loadJob as mi, liveUpdateFor as mn, kissOutcome as mo, pruneNotifications as mr, cliEntry as mt, MCP_TOOL_NAMES as n, REVIEWER_OUTCOMES as na, checkReview as ni, COMMIT_REVIEW_CANNOT_UPDATE as nn, promptGrade as no, branchOf as nr, isDelivered as nt, liveJobsFor as o, outcomeDefinition as oa, forgeResolver as oi, cancellableHere as on, wasReassessed as oo, createPrState as or, lineageKeysFor as ot, track as p, LIFECYCLES as pa, isTerminal as pi, UpdateAlreadyRunningError as pn, WITHHELD_REASON as po, moveIds as pr, RUNNER_MARKER as pt, omittedText as q, assessmentLookup as qa, reviewedCommit as qi, storedScopes as qn, createCollection as qo, holdWorktree as qr, workspaceUrl as qt, deleteLineage as r, appendOutcome as ra, followedBranch as ri, FETCH_TIMEOUT_MS as rn, reassessIn as ro, subjectOf as rr, missedDelivery as rt, wasBlocked as s, outcomeLabel as sa, fillFileUrlTemplate as si, mergeUpdateInto as sn, followUpFindingId as so, createPrUpdate as sr, settleCollections as st, MCP_SERVER_INFO_NAME as t, analysisSnapshot as ta, checkCodeFreshness as ti, writePortPreference as tn, isOffered as to, announceJob as tr, STORE_ROOT as ts, deliver as tt, recordKissRun as u, parseOutcomes as ua, attemptIdOf as ui, updateReview as un, extractJsonText as uo, markAllRead as ur, queueForReview as ut, heldRuns as v, lifecycleItemLabel as va, isLiveJob as vi, prepareReview as vn, TargetError as vo, retireNotification as vr, modelProvenanceOf as vt, unsupportedReason as w, sectionOf as wa, driverGone as wi, readEvents as wn, git as wo, ASK_REREAD_CEILING_MS as wr, findingRefs as wt, batchIdentity as x, lifecyclePillLabel as xa, progressSteps as xi, runJob as xn, AmbiguousRefError as xo, unreadCount as xr, humanizeModelId as xt, reportRun as y, lifecycleName$1 as ya, isTerminalJobPhase as yi, settleReview as yn, isCommitReview as yo, toneOf as yr, provenanceDetailLines as yt, collectionsLine as z, recommendationsOf as za, loadWorkspace as zi, formatDuration as zn, parkedReason as zo, escapeHtml as zr, ensureServer as zt };
|