@effect-agent/pr-review 0.1.0-beta.23 → 0.1.0-beta.24
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/README.md +12 -5
- package/dist/action.d.mts +3 -3
- package/dist/action.mjs +11 -2
- package/dist/action.mjs.map +1 -1
- package/dist/cli.mjs +2 -2
- package/dist/{fan-out-BJBTAYuh.d.mts → fan-out-Bi1v0VaU.d.mts} +56 -4
- package/dist/{github-BbwYzNrC.mjs → github-C6jrBLA2.mjs} +222 -60
- package/dist/github-C6jrBLA2.mjs.map +1 -0
- package/dist/index.d.mts +17 -15
- package/dist/index.mjs +3 -3
- package/dist/{providers-NyP-4rS6.mjs → providers-2Jao2ZAX.mjs} +14 -5
- package/dist/providers-2Jao2ZAX.mjs.map +1 -0
- package/dist/testing.d.mts +3 -1
- package/dist/testing.mjs +3 -2
- package/dist/testing.mjs.map +1 -1
- package/package.json +2 -2
- package/src/action.ts +18 -0
- package/src/internal/factory.ts +4 -4
- package/src/internal/fan-out.ts +152 -10
- package/src/internal/fingerprint.ts +16 -10
- package/src/internal/fixtures.ts +6 -0
- package/src/internal/github.ts +36 -7
- package/src/internal/review-state.ts +181 -90
- package/src/internal/run.ts +20 -0
- package/dist/github-BbwYzNrC.mjs.map +0 -1
- package/dist/providers-NyP-4rS6.mjs.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Context, DateTime, Effect, Encoding, Layer, Option, Redacted, Result, Schema } from "effect";
|
|
1
|
+
import { Context, Crypto, DateTime, Effect, Encoding, Layer, Option, Redacted, Result, Schema } from "effect";
|
|
2
2
|
import { Agent, AgentPolicy, AgentRuntime, ToolExecutionClass, ToolResultBounds } from "effect-agent";
|
|
3
3
|
import { Tool, Toolkit } from "effect/unstable/ai";
|
|
4
4
|
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
|
|
@@ -1569,7 +1569,11 @@ const reviewUnit = (binding, unit, passes, input) => Effect.gen(function* () {
|
|
|
1569
1569
|
completedSpecialistPasses,
|
|
1570
1570
|
requiredVerificationPasses,
|
|
1571
1571
|
completedVerificationPasses,
|
|
1572
|
-
unreviewedPaths: failedPasses.length > 0 ? unit.paths : []
|
|
1572
|
+
unreviewedPaths: failedPasses.length > 0 ? unit.paths : [],
|
|
1573
|
+
unreviewedPasses: failedPasses.map((pass) => ({
|
|
1574
|
+
stage: pass.stage,
|
|
1575
|
+
paths: unit.paths
|
|
1576
|
+
}))
|
|
1573
1577
|
};
|
|
1574
1578
|
});
|
|
1575
1579
|
const countNoun = (count, noun) => `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
@@ -1584,6 +1588,90 @@ const composeSummary = (plan, assurance) => {
|
|
|
1584
1588
|
parts.push("No configured pipeline can prove absence of defects; this describes settled work only.");
|
|
1585
1589
|
return parts.join(" ").slice(0, 4e3);
|
|
1586
1590
|
};
|
|
1591
|
+
const remapPlanUnitIds = (plan, offset) => {
|
|
1592
|
+
if (offset === 0) return plan;
|
|
1593
|
+
const units = plan.units.map((unit, index) => ReviewUnit.make({
|
|
1594
|
+
...unit,
|
|
1595
|
+
unitId: `unit-${String(offset + index + 1).padStart(3, "0")}`
|
|
1596
|
+
}));
|
|
1597
|
+
const mappedIds = /* @__PURE__ */ new Map();
|
|
1598
|
+
for (const [index, unit] of plan.units.entries()) {
|
|
1599
|
+
const remapped = units[index];
|
|
1600
|
+
if (remapped !== void 0) mappedIds.set(unit.unitId, remapped.unitId);
|
|
1601
|
+
}
|
|
1602
|
+
return ReviewUnitPlan.make({
|
|
1603
|
+
...plan,
|
|
1604
|
+
units,
|
|
1605
|
+
discoveryPasses: plan.discoveryPasses.map((pass) => {
|
|
1606
|
+
const unitId = mappedIds.get(pass.unitId) ?? pass.unitId;
|
|
1607
|
+
return ReviewDiscoveryPass.make({
|
|
1608
|
+
...pass,
|
|
1609
|
+
unitId,
|
|
1610
|
+
passId: `${unitId}${pass.passId.slice(pass.unitId.length)}`
|
|
1611
|
+
});
|
|
1612
|
+
})
|
|
1613
|
+
});
|
|
1614
|
+
};
|
|
1615
|
+
const scheduleFanOutWork = (input) => {
|
|
1616
|
+
const retryPathSet = new Set(input.retry?.paths ?? []);
|
|
1617
|
+
const retryStages = new Set(input.retry?.stages ?? []);
|
|
1618
|
+
if (retryPathSet.size === 0) {
|
|
1619
|
+
const plan = planReviewUnits(input.files, { totalChangedFiles: input.totalChangedFiles });
|
|
1620
|
+
const passesByUnit = /* @__PURE__ */ new Map();
|
|
1621
|
+
for (const pass of plan.discoveryPasses) {
|
|
1622
|
+
const passes = passesByUnit.get(pass.unitId) ?? [];
|
|
1623
|
+
passes.push(pass);
|
|
1624
|
+
passesByUnit.set(pass.unitId, passes);
|
|
1625
|
+
}
|
|
1626
|
+
return {
|
|
1627
|
+
plan,
|
|
1628
|
+
passesByUnit,
|
|
1629
|
+
overflowRetryPaths: []
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
const freshFiles = input.files.filter((file) => !retryPathSet.has(file.path));
|
|
1633
|
+
const retryFiles = input.files.filter((file) => retryPathSet.has(file.path));
|
|
1634
|
+
const freshPlan = planReviewUnits(freshFiles, { totalChangedFiles: input.totalChangedFiles });
|
|
1635
|
+
const retryPlan = remapPlanUnitIds(planReviewUnits(retryFiles, { totalChangedFiles: input.totalChangedFiles }), freshPlan.units.length);
|
|
1636
|
+
const acceptedFresh = freshPlan.units.slice(0, 8);
|
|
1637
|
+
const acceptedRetry = retryPlan.units.slice(0, Math.max(0, 8 - acceptedFresh.length));
|
|
1638
|
+
const overflowRetryPaths = retryPlan.units.slice(acceptedRetry.length).flatMap((unit) => [...unit.paths]);
|
|
1639
|
+
const retryPassFilter = (pass) => {
|
|
1640
|
+
const stage = pass.perspective === "risk-specialist" ? "specialist" : "discovery";
|
|
1641
|
+
return retryStages.has(stage);
|
|
1642
|
+
};
|
|
1643
|
+
const acceptedRetryIds = new Set(acceptedRetry.map((unit) => unit.unitId));
|
|
1644
|
+
let retryPasses = retryPlan.discoveryPasses.filter((pass) => acceptedRetryIds.has(pass.unitId) && retryPassFilter(pass));
|
|
1645
|
+
if (retryStages.has("verification") && !retryStages.has("discovery") && !retryStages.has("specialist")) retryPasses = retryPlan.discoveryPasses.filter((pass) => acceptedRetryIds.has(pass.unitId));
|
|
1646
|
+
const acceptedFreshIds = new Set(acceptedFresh.map((unit) => unit.unitId));
|
|
1647
|
+
const discoveryPasses = [...freshPlan.discoveryPasses.filter((pass) => acceptedFreshIds.has(pass.unitId)), ...retryPasses];
|
|
1648
|
+
const plan = ReviewUnitPlan.make({
|
|
1649
|
+
totalFiles: input.files.length,
|
|
1650
|
+
truncated: freshPlan.truncated || retryPlan.truncated,
|
|
1651
|
+
units: [...acceptedFresh, ...acceptedRetry],
|
|
1652
|
+
discoveryPasses,
|
|
1653
|
+
undiffablePaths: [.../* @__PURE__ */ new Set([...freshPlan.undiffablePaths, ...retryPlan.undiffablePaths])].sort(),
|
|
1654
|
+
partialEvidencePaths: [.../* @__PURE__ */ new Set([...freshPlan.partialEvidencePaths, ...retryPlan.partialEvidencePaths])].sort(),
|
|
1655
|
+
unassignedEvidenceShardCount: freshPlan.unassignedEvidenceShardCount + retryPlan.unassignedEvidenceShardCount,
|
|
1656
|
+
unassignedEvidenceShardIds: [...freshPlan.unassignedEvidenceShardIds, ...retryPlan.unassignedEvidenceShardIds].slice(0, 96),
|
|
1657
|
+
unassignedPaths: [.../* @__PURE__ */ new Set([
|
|
1658
|
+
...freshPlan.unassignedPaths,
|
|
1659
|
+
...retryPlan.unassignedPaths,
|
|
1660
|
+
...overflowRetryPaths
|
|
1661
|
+
])].sort()
|
|
1662
|
+
});
|
|
1663
|
+
const passesByUnit = /* @__PURE__ */ new Map();
|
|
1664
|
+
for (const pass of discoveryPasses) {
|
|
1665
|
+
const passes = passesByUnit.get(pass.unitId) ?? [];
|
|
1666
|
+
passes.push(pass);
|
|
1667
|
+
passesByUnit.set(pass.unitId, passes);
|
|
1668
|
+
}
|
|
1669
|
+
return {
|
|
1670
|
+
plan,
|
|
1671
|
+
passesByUnit,
|
|
1672
|
+
overflowRetryPaths
|
|
1673
|
+
};
|
|
1674
|
+
};
|
|
1587
1675
|
/**
|
|
1588
1676
|
* Run the complete host-scheduled fan-out pipeline over one selected
|
|
1589
1677
|
* changeset snapshot: plan, independent discovery, exact verification, and a
|
|
@@ -1591,13 +1679,7 @@ const composeSummary = (plan, assurance) => {
|
|
|
1591
1679
|
* only. The verdict is derived from confirmed severities, never model prose.
|
|
1592
1680
|
*/
|
|
1593
1681
|
const runFanOutReview = (binding, input) => Effect.gen(function* () {
|
|
1594
|
-
const plan
|
|
1595
|
-
const passesByUnit = /* @__PURE__ */ new Map();
|
|
1596
|
-
for (const pass of plan.discoveryPasses) {
|
|
1597
|
-
const passes = passesByUnit.get(pass.unitId) ?? [];
|
|
1598
|
-
passes.push(pass);
|
|
1599
|
-
passesByUnit.set(pass.unitId, passes);
|
|
1600
|
-
}
|
|
1682
|
+
const { plan, passesByUnit, overflowRetryPaths } = scheduleFanOutWork(input);
|
|
1601
1683
|
const outcomes = yield* Effect.forEach(plan.units, (unit) => reviewUnit(binding, unit, passesByUnit.get(unit.unitId) ?? [], input), { concurrency: 4 });
|
|
1602
1684
|
const failedPasses = outcomes.flatMap((outcome) => outcome.failedPasses);
|
|
1603
1685
|
const unsettledCandidates = outcomes.reduce((total, outcome) => total + outcome.unsettledCandidates, 0);
|
|
@@ -1642,6 +1724,14 @@ const runFanOutReview = (binding, input) => Effect.gen(function* () {
|
|
|
1642
1724
|
...plan.partialEvidencePaths,
|
|
1643
1725
|
...plan.undiffablePaths
|
|
1644
1726
|
])].sort(),
|
|
1727
|
+
unreviewedPasses: [...outcomes.flatMap((outcome) => outcome.unreviewedPasses), ...overflowRetryPaths.length === 0 ? [] : (input.retry?.stages.length ? input.retry.stages : [
|
|
1728
|
+
"discovery",
|
|
1729
|
+
"specialist",
|
|
1730
|
+
"verification"
|
|
1731
|
+
]).map((stage) => ({
|
|
1732
|
+
stage,
|
|
1733
|
+
paths: overflowRetryPaths
|
|
1734
|
+
}))],
|
|
1645
1735
|
turns: outcomes.reduce((total, outcome) => total + outcome.turns, 0)
|
|
1646
1736
|
};
|
|
1647
1737
|
});
|
|
@@ -1660,10 +1750,10 @@ const extractFingerprint = (body) => {
|
|
|
1660
1750
|
for (const match of body.matchAll(MARKER_PATTERN)) last = match[1];
|
|
1661
1751
|
return last;
|
|
1662
1752
|
};
|
|
1663
|
-
/**
|
|
1664
|
-
const sha256Hex =
|
|
1665
|
-
const digest =
|
|
1666
|
-
return
|
|
1753
|
+
/** SHA-256 via `Crypto.Crypto`; unavailable crypto is a defect, not an expected failure. */
|
|
1754
|
+
const sha256Hex = Effect.fn("sha256Hex")(function* (text) {
|
|
1755
|
+
const digest = yield* (yield* Crypto.Crypto).digest("SHA-256", new TextEncoder().encode(text)).pipe(Effect.orDie);
|
|
1756
|
+
return Encoding.encodeHex(digest);
|
|
1667
1757
|
});
|
|
1668
1758
|
const FIELD = "\0";
|
|
1669
1759
|
const RECORD = "";
|
|
@@ -1687,6 +1777,8 @@ const canonicalChangeset = (files) => files.map((file) => `${file.path}${FIELD}$
|
|
|
1687
1777
|
* not carry.
|
|
1688
1778
|
*/
|
|
1689
1779
|
const computeChangesetFingerprint = (files, signature) => sha256Hex(`${canonicalChangeset(files)}${SECTION}${signature}`);
|
|
1780
|
+
/** Profile fingerprints are SHA-256 over configuration-only signatures. */
|
|
1781
|
+
const computeProfileFingerprint = (signature) => sha256Hex(signature);
|
|
1690
1782
|
//#endregion
|
|
1691
1783
|
//#region src/internal/review-state.ts
|
|
1692
1784
|
const ReviewMode = Schema.Literals(["incremental", "final"]);
|
|
@@ -1711,6 +1803,19 @@ var StoredReviewConcern = class extends Schema.Class("@effect-agent/pr-review/St
|
|
|
1711
1803
|
}) {};
|
|
1712
1804
|
/** The carried-scope bound; a run that cannot fit its leftovers publishes no state. */
|
|
1713
1805
|
const MAX_STORED_UNREVIEWED_PATHS = 100;
|
|
1806
|
+
/** Failed-pass records stored beside the leftover paths; one per unit stage. */
|
|
1807
|
+
const MAX_STORED_UNREVIEWED_PASSES = 24;
|
|
1808
|
+
/** Stages a leftover path may need retried without a second general discovery. */
|
|
1809
|
+
const UnreviewedStage = Schema.Literals([
|
|
1810
|
+
"discovery",
|
|
1811
|
+
"specialist",
|
|
1812
|
+
"verification"
|
|
1813
|
+
]);
|
|
1814
|
+
/** One failed fan-out pass whose paths should be retried, not rediscovered. */
|
|
1815
|
+
var StoredUnreviewedPass = class extends Schema.Class("@effect-agent/pr-review/StoredUnreviewedPass")({
|
|
1816
|
+
stage: UnreviewedStage,
|
|
1817
|
+
paths: Schema.Array(ChangedPath).check(Schema.isMinLength(1)).check(Schema.isMaxLength(12))
|
|
1818
|
+
}) {};
|
|
1714
1819
|
/**
|
|
1715
1820
|
* Versioned state embedded after EVERY completed run that can be signed. The
|
|
1716
1821
|
* head plus full-scope fingerprint forms an incremental baseline; an absent
|
|
@@ -1741,6 +1846,12 @@ var ReviewState = class extends Schema.Class("@effect-agent/pr-review/ReviewStat
|
|
|
1741
1846
|
/** Retryable review gaps carried into the next incremental run's scope. */
|
|
1742
1847
|
unreviewedPaths: Schema.Array(ChangedPath).check(Schema.isMaxLength(100)),
|
|
1743
1848
|
/**
|
|
1849
|
+
* Which failed pass produced those leftovers. Absent on state-v2 markers
|
|
1850
|
+
* written before this field existed; those leftovers still re-enter scope
|
|
1851
|
+
* but cannot skip rediscovery. Present (including empty) on new markers.
|
|
1852
|
+
*/
|
|
1853
|
+
unreviewedPasses: Schema.optionalKey(Schema.Array(StoredUnreviewedPass).check(Schema.isMaxLength(24))),
|
|
1854
|
+
/**
|
|
1744
1855
|
* True only when the producing run had complete input coverage, no
|
|
1745
1856
|
* unsettled pass, and nothing carried. Skip-unchanged authority: an
|
|
1746
1857
|
* unchanged patch may skip re-review only over a settled state.
|
|
@@ -1883,11 +1994,15 @@ const fullSelection = (input) => ({
|
|
|
1883
1994
|
reason: input.reason,
|
|
1884
1995
|
files: input.files,
|
|
1885
1996
|
affectedPaths: input.files.flatMap((file) => file.previousPath === void 0 ? [file.path] : [file.path, file.previousPath]),
|
|
1997
|
+
retryPaths: [],
|
|
1998
|
+
retryStages: [],
|
|
1886
1999
|
totalFiles: input.totalFiles,
|
|
1887
2000
|
baselineSha: void 0,
|
|
1888
2001
|
priorState: void 0,
|
|
1889
2002
|
profileFingerprint: input.profileFingerprint
|
|
1890
2003
|
});
|
|
2004
|
+
/** Three-dot lineage from the reviewed head to the current head is usable. */
|
|
2005
|
+
const isLineageAncestor = (comparison, priorState, currentHeadSha) => comparison.baseSha === priorState.reviewedHeadSha && comparison.headSha === currentHeadSha && comparison.mergeBaseSha === priorState.reviewedHeadSha && !comparison.truncated && (comparison.status === "ahead" || comparison.status === "identical");
|
|
1891
2006
|
/**
|
|
1892
2007
|
* Validate that persisted state belongs to this exact PR/base lineage and the
|
|
1893
2008
|
* same review profile. A mismatch is a full-review reason, never an error that
|
|
@@ -1900,6 +2015,52 @@ const validateReviewState = (state, current, profileFingerprint) => {
|
|
|
1900
2015
|
if (state.headRef !== current.headRef) return "the pull request head ref changed";
|
|
1901
2016
|
if (state.profileFingerprint !== profileFingerprint) return "the reviewer profile or model configuration changed";
|
|
1902
2017
|
};
|
|
2018
|
+
const filePaths = (file) => file.previousPath === void 0 ? [file.path] : [file.path, file.previousPath];
|
|
2019
|
+
const incrementalFromDelta = (input) => {
|
|
2020
|
+
const currentPaths = new Set(input.fullFiles.flatMap(filePaths));
|
|
2021
|
+
const affectedPaths = /* @__PURE__ */ new Set([...input.deltaFiles.flatMap(filePaths), ...input.extraAffectedPaths ?? []]);
|
|
2022
|
+
const selectedByPath = /* @__PURE__ */ new Map();
|
|
2023
|
+
for (const file of input.deltaFiles) if (currentPaths.has(file.path) || file.previousPath !== void 0 && currentPaths.has(file.previousPath)) selectedByPath.set(file.path, file);
|
|
2024
|
+
const carriedPaths = input.priorState.unreviewedPaths.filter((path) => currentPaths.has(path));
|
|
2025
|
+
const surgical = input.priorState.unreviewedPasses !== void 0;
|
|
2026
|
+
const retryOnly = /* @__PURE__ */ new Set();
|
|
2027
|
+
const retryStages = /* @__PURE__ */ new Set();
|
|
2028
|
+
for (const path of carriedPaths) {
|
|
2029
|
+
if (affectedPaths.has(path)) continue;
|
|
2030
|
+
retryOnly.add(path);
|
|
2031
|
+
if (surgical) {
|
|
2032
|
+
for (const pass of input.priorState.unreviewedPasses ?? []) if (pass.paths.includes(path)) retryStages.add(pass.stage);
|
|
2033
|
+
} else affectedPaths.add(path);
|
|
2034
|
+
}
|
|
2035
|
+
if (surgical && retryStages.has("verification") && !retryStages.has("discovery") && !retryStages.has("specialist")) {
|
|
2036
|
+
retryStages.add("discovery");
|
|
2037
|
+
retryStages.add("specialist");
|
|
2038
|
+
}
|
|
2039
|
+
if (surgical && retryOnly.size > 0 && retryStages.size === 0) {
|
|
2040
|
+
for (const path of retryOnly) affectedPaths.add(path);
|
|
2041
|
+
retryStages.add("discovery");
|
|
2042
|
+
retryStages.add("specialist");
|
|
2043
|
+
retryStages.add("verification");
|
|
2044
|
+
}
|
|
2045
|
+
if (carriedPaths.length > 0 || (input.extraAffectedPaths?.length ?? 0) > 0) {
|
|
2046
|
+
for (const file of input.fullFiles) if (affectedPaths.has(file.path) || file.previousPath !== void 0 && affectedPaths.has(file.previousPath) || retryOnly.has(file.path) || file.previousPath !== void 0 && retryOnly.has(file.previousPath)) selectedByPath.set(file.path, file);
|
|
2047
|
+
}
|
|
2048
|
+
const selectedFiles = [...selectedByPath.values()].sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
|
|
2049
|
+
const leftoverCount = [...retryOnly].filter((path) => !affectedPaths.has(path)).length;
|
|
2050
|
+
const carriedReason = leftoverCount > 0 && surgical ? `; retrying ${leftoverCount} unchanged leftover path(s) without rediscovery` : carriedPaths.length > 0 ? `; retrying ${carriedPaths.length} carried unreviewed path(s)` : "";
|
|
2051
|
+
return {
|
|
2052
|
+
mode: "incremental",
|
|
2053
|
+
reason: `${input.reason}${carriedReason}`,
|
|
2054
|
+
files: selectedFiles,
|
|
2055
|
+
affectedPaths: [...affectedPaths].sort(),
|
|
2056
|
+
retryPaths: [...retryOnly].filter((path) => !affectedPaths.has(path)).sort(),
|
|
2057
|
+
retryStages: [...retryStages].sort(),
|
|
2058
|
+
totalFiles: selectedFiles.length,
|
|
2059
|
+
baselineSha: input.priorState.reviewedHeadSha,
|
|
2060
|
+
priorState: input.priorState,
|
|
2061
|
+
profileFingerprint: input.profileFingerprint
|
|
2062
|
+
};
|
|
2063
|
+
};
|
|
1903
2064
|
/** Pure, deterministic range selection with conservative full-review fallbacks. */
|
|
1904
2065
|
const selectReviewRange = (input) => {
|
|
1905
2066
|
const full = (reason) => fullSelection({
|
|
@@ -1914,42 +2075,38 @@ const selectReviewRange = (input) => {
|
|
|
1914
2075
|
const invalid = validateReviewState(input.priorState, input.current, input.profileFingerprint);
|
|
1915
2076
|
if (invalid !== void 0) return full(invalid);
|
|
1916
2077
|
const comparison = input.comparison;
|
|
1917
|
-
if (comparison
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
for (const file of baseComparison.files) {
|
|
1927
|
-
affectedPaths.add(file.path);
|
|
1928
|
-
if (file.previousPath !== void 0) affectedPaths.add(file.previousPath);
|
|
2078
|
+
if (comparison !== void 0 && isLineageAncestor(comparison, input.priorState, input.current.headSha)) {
|
|
2079
|
+
const extraAffected = [];
|
|
2080
|
+
let baseReason = "";
|
|
2081
|
+
if (input.priorState.baseSha !== input.current.baseSha) {
|
|
2082
|
+
const baseComparison = input.baseComparison;
|
|
2083
|
+
if (baseComparison === void 0) return full("the pull request base changed and its lineage comparison was unavailable");
|
|
2084
|
+
if (baseComparison.baseSha !== input.priorState.baseSha || baseComparison.headSha !== input.current.baseSha || baseComparison.mergeBaseSha !== input.priorState.baseSha || baseComparison.status !== "ahead" && baseComparison.status !== "identical" || baseComparison.truncated) return full("the pull request base changed materially or exceeded the comparison bound");
|
|
2085
|
+
for (const file of baseComparison.files) extraAffected.push(...filePaths(file));
|
|
2086
|
+
baseReason = `; base advanced from ${input.priorState.baseSha.slice(0, 7)} and overlapping PR paths were included`;
|
|
1929
2087
|
}
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
for (const file of input.fullFiles) if (rescuePaths && (affectedPaths.has(file.path) || file.previousPath !== void 0 && affectedPaths.has(file.previousPath)) || carriedPaths.has(file.path) || file.previousPath !== void 0 && carriedPaths.has(file.previousPath)) selectedByPath.set(file.path, file);
|
|
2088
|
+
return incrementalFromDelta({
|
|
2089
|
+
current: input.current,
|
|
2090
|
+
fullFiles: input.fullFiles,
|
|
2091
|
+
profileFingerprint: input.profileFingerprint,
|
|
2092
|
+
priorState: input.priorState,
|
|
2093
|
+
deltaFiles: comparison.files,
|
|
2094
|
+
extraAffectedPaths: extraAffected,
|
|
2095
|
+
reason: `changes since reviewed head ${input.priorState.reviewedHeadSha.slice(0, 7)}${baseReason}`
|
|
2096
|
+
});
|
|
1940
2097
|
}
|
|
1941
|
-
const
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
files: selectedFiles,
|
|
1947
|
-
affectedPaths: [...affectedPaths].sort(),
|
|
1948
|
-
totalFiles: selectedFiles.length,
|
|
1949
|
-
baselineSha: input.priorState.reviewedHeadSha,
|
|
2098
|
+
const contentComparison = input.contentComparison;
|
|
2099
|
+
if (contentComparison !== void 0 && !contentComparison.truncated) return incrementalFromDelta({
|
|
2100
|
+
current: input.current,
|
|
2101
|
+
fullFiles: input.fullFiles,
|
|
2102
|
+
profileFingerprint: input.profileFingerprint,
|
|
1950
2103
|
priorState: input.priorState,
|
|
1951
|
-
|
|
1952
|
-
|
|
2104
|
+
deltaFiles: contentComparison.files,
|
|
2105
|
+
reason: `rewritten history; contents changed since reviewed head ${input.priorState.reviewedHeadSha.slice(0, 7)}`
|
|
2106
|
+
});
|
|
2107
|
+
if (comparison === void 0) return full("the incremental head comparison was unavailable");
|
|
2108
|
+
if (comparison.truncated) return full("the incremental comparison exceeded GitHub's file bound");
|
|
2109
|
+
return full("the prior reviewed head is not an ancestor of the current head");
|
|
1953
2110
|
};
|
|
1954
2111
|
/** Per-run context consumed by orchestration and publication, not by the model. */
|
|
1955
2112
|
var ReviewExecutionContext = class extends Context.Service()("@effect-agent/pr-review/ReviewExecutionContext") {};
|
|
@@ -1983,11 +2140,6 @@ const selectedPullRequestSourceLayer = (selection) => Layer.effect(PullRequestSo
|
|
|
1983
2140
|
}))
|
|
1984
2141
|
});
|
|
1985
2142
|
}));
|
|
1986
|
-
/** Profile fingerprints are SHA-256 over configuration-only signatures. */
|
|
1987
|
-
const computeProfileFingerprint = (signature) => Effect.promise(async () => {
|
|
1988
|
-
const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(signature));
|
|
1989
|
-
return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1990
|
-
});
|
|
1991
2143
|
/** Build the full-surface mission used only to resolve profile guidance. */
|
|
1992
2144
|
const buildProfileMission = (metadata, files) => ReviewMission.make({
|
|
1993
2145
|
repository: metadata.repository,
|
|
@@ -2250,8 +2402,8 @@ const GitHubReviewCommentWire = Schema.Struct({
|
|
|
2250
2402
|
node_id: Schema.String,
|
|
2251
2403
|
path: Schema.String,
|
|
2252
2404
|
body: Schema.String,
|
|
2253
|
-
line: Schema.NullOr(Schema.Int),
|
|
2254
|
-
original_line: Schema.NullOr(Schema.Int),
|
|
2405
|
+
line: Schema.optionalKey(Schema.NullOr(Schema.Int)),
|
|
2406
|
+
original_line: Schema.optionalKey(Schema.NullOr(Schema.Int)),
|
|
2255
2407
|
start_line: Schema.optionalKey(Schema.NullOr(Schema.Int)),
|
|
2256
2408
|
original_start_line: Schema.optionalKey(Schema.NullOr(Schema.Int))
|
|
2257
2409
|
});
|
|
@@ -2492,8 +2644,9 @@ const gitHubReviewRetirementHostLayer = Layer.effect(ReviewRetirementHost)(Effec
|
|
|
2492
2644
|
url: `${prefix}/reviews/${reviewId}/comments`,
|
|
2493
2645
|
decode: decodeRetirement(GitHubReviewCommentsPageWire, "listReviewCommentsForRetirement")
|
|
2494
2646
|
}).pipe(Effect.map((comments) => comments.map((comment) => {
|
|
2495
|
-
const
|
|
2496
|
-
const
|
|
2647
|
+
const positiveLine = (value) => value !== void 0 && value !== null && value > 0 ? value : null;
|
|
2648
|
+
const endLine = positiveLine(comment.line ?? comment.original_line);
|
|
2649
|
+
const startLine = positiveLine(comment.start_line ?? comment.original_start_line) ?? endLine;
|
|
2497
2650
|
return RetirableReviewComment.make({
|
|
2498
2651
|
nodeId: comment.node_id,
|
|
2499
2652
|
path: comment.path,
|
|
@@ -2583,8 +2736,8 @@ const gitHubPriorReviewsLayer = Layer.effect(PriorReviews)(Effect.gen(function*
|
|
|
2583
2736
|
latestState
|
|
2584
2737
|
};
|
|
2585
2738
|
}).pipe(Effect.provideService(HttpClient.HttpClient, client));
|
|
2586
|
-
const
|
|
2587
|
-
const wire = yield* (yield* HttpClient.execute(withCommonHeaders(HttpClientRequest.get(`${target.apiUrl}/repos/${target.repository}/compare/${encodeURIComponent(baseSha)}
|
|
2739
|
+
const compareCommits = (baseSha, headSha, separator) => Effect.gen(function* () {
|
|
2740
|
+
const wire = yield* (yield* HttpClient.execute(withCommonHeaders(HttpClientRequest.get(`${target.apiUrl}/repos/${target.repository}/compare/${encodeURIComponent(baseSha)}${separator}${encodeURIComponent(headSha)}`).pipe(HttpClientRequest.acceptJson), target.token)).pipe(Effect.flatMap(HttpClientResponse.filterStatusOk), Effect.mapError(asLookupFailure))).json.pipe(Effect.mapError(asLookupFailure), Effect.flatMap((body) => Schema.decodeUnknownEffect(GitHubCompareWire)(body).pipe(Effect.mapError(asLookupFailure))));
|
|
2588
2741
|
const files = wire.files.map(toChangedFile);
|
|
2589
2742
|
return ReviewHeadComparison.make({
|
|
2590
2743
|
status: wire.status,
|
|
@@ -2595,13 +2748,22 @@ const gitHubPriorReviewsLayer = Layer.effect(PriorReviews)(Effect.gen(function*
|
|
|
2595
2748
|
truncated: files.length >= 300
|
|
2596
2749
|
});
|
|
2597
2750
|
}).pipe(Effect.provideService(HttpClient.HttpClient, client));
|
|
2751
|
+
const compareTrees = (baseSha, headSha) => compareCommits(baseSha, headSha, "..").pipe(Effect.map((comparison) => ReviewHeadComparison.make({
|
|
2752
|
+
status: comparison.status === "identical" ? "identical" : "ahead",
|
|
2753
|
+
baseSha,
|
|
2754
|
+
headSha,
|
|
2755
|
+
mergeBaseSha: baseSha,
|
|
2756
|
+
files: comparison.files,
|
|
2757
|
+
truncated: comparison.truncated
|
|
2758
|
+
})));
|
|
2598
2759
|
return PriorReviews.of({
|
|
2599
2760
|
latestFingerprint: readMarkers(Option.none()).pipe(Effect.map((markers) => markers.latestFingerprint)),
|
|
2600
2761
|
latestState: Effect.gen(function* () {
|
|
2601
2762
|
const authenticator = yield* ReviewStateAuthenticator;
|
|
2602
2763
|
return yield* readMarkers(Option.some(authenticator)).pipe(Effect.map((markers) => markers.latestState));
|
|
2603
2764
|
}),
|
|
2604
|
-
compareHeads
|
|
2765
|
+
compareHeads: (baseSha, headSha) => compareCommits(baseSha, headSha, "..."),
|
|
2766
|
+
compareTrees
|
|
2605
2767
|
});
|
|
2606
2768
|
}));
|
|
2607
2769
|
/**
|
|
@@ -2614,6 +2776,6 @@ const fingerprintUnchanged = (current) => Effect.gen(function* () {
|
|
|
2614
2776
|
return Option.isSome(latest) && latest.value === current;
|
|
2615
2777
|
});
|
|
2616
2778
|
//#endregion
|
|
2617
|
-
export {
|
|
2779
|
+
export { extractFingerprint as $, ChangedFileStatus as $n, ReviewAssurance as $t, ReviewState as A, ReviewToolkit as An, MAX_MERGED_FINDINGS as At, fromStoredConcern as B, readFileDiffHandler as Bn, ReviewRiskCategory as Bt, MAX_REVIEW_STATE_MARKER_CHARS as C, PullRequestReviewer as Cn, defaultFileReviewerPolicy as Ct, ReviewHeadComparison as D, ReviewConcern as Dn, reviewCandidateSubjectKey as Dt, ReviewExecutionContext as E, ReadFileDiff as En, makeFileReviewerInstructions as Et, StoredReviewConcern as F, defaultReviewPolicy as Fn, ReviewDiscoveryPass as Ft, toStoredConcern as G, MAX_FILE_CHARS as Gn, UNIT_EVIDENCE_CHAR_BUDGET as Gt, isLineageAncestor as H, resolveGuidance as Hn, ReviewUnitId as Ht, StoredReviewFinding as I, fileDiffView as In, ReviewDiscoveryPerspective as It, validateReviewState as J, PullRequestSourceFailure as Jn, planReviewUnits as Jt, toStoredFinding as K, PullRequestMetadata as Kn, classifyReviewRisks as Kt, StoredUnreviewedPass as L, fileReviewEvidenceChunks as Ln, ReviewEvidenceShard as Lt, ReviewStateAuthenticator as M, ReviewVerdict as Mn, MAX_REVIEW_UNITS as Mt, ReviewStateMarker as N, WalkthroughEntry as Nn, MAX_UNIT_EVIDENCE_SHARDS as Nt, ReviewMode as O, ReviewFinding as On, runFanOutReview as Ot, ReviewStateMarkerTooLarge as P, clampMaxFindings as Pn, MAX_UNIT_FILES as Pt, computeProfileFingerprint as Q, ChangedFile as Qn, FailedReviewUnit as Qt, UnreviewedStage as R, listChangedFilesHandler as Rn, ReviewEvidenceShardId as Rt, GitCommitSha as S, MAX_WALKTHROUGH_SUMMARY_CHARS as Sn, confirmedFindingForPublication as St, MAX_STORED_UNREVIEWED_PATHS as T, ReadFile as Tn, makeFileReviewerDefinition as Tt, selectReviewRange as U, reviewInstructions as Un, ReviewUnitPlan as Ut, fromStoredFinding as V, readFileHandler as Vn, ReviewUnit as Vt, selectedPullRequestSourceLayer as W, MAX_CHANGED_FILES as Wn, UNIT_CHANGED_LINE_BUDGET as Wt, FINGERPRINT_MARKER_LENGTH as X, normalizeRepoRelativePath as Xn, rankAndDedupeFindings as Xt, webCryptoReviewStateAuthenticatorLayer as Y, ReviewInputViolation as Yn, rankAndDedupeConcerns as Yt, computeChangesetFingerprint as Z, anchorViolation as Zn, FailedReviewPass as Zt, ReviewRetirementHost as _, ListChangedFilesQuery as _n, ReviewCandidateId as _t, PriorReviews as a, fanOutInputCoverage as an, isReviewableFile as ar, FileReviewEvidence as at, hasReviewMetadataMarker as b, MAX_PATCH_CHARS as bn, ReviewWorkPhase as bt, fingerprintUnchanged as c, ChangedFilesView as cn, FileReviewer as ct, gitHubReviewPublisherLayer as d, FileDiffView as dn, MAX_CHILD_FINDINGS as dt, ReviewCoverage as en, ChangedPath as er, renderFingerprintMarker as et, gitHubReviewRetirementHostLayer as f, FileSlice as fn, MAX_FILE_REVIEW_TOOL_CALLS as ft, ReviewRetirementFailure as g, ListChangedFiles as gn, ReviewCandidate as gt, RetirableReviewComment as h, FindingSeverity as hn, REVIEW_UNIT_CONCURRENCY as ht, PriorReviewLookupFailure as i, compatibilityCoverage as in, hasReviewableContent as ir, FileReviewBrief as it, ReviewStateAuthenticationFailure as j, ReviewToolkitLayer as jn, MAX_REPORTED_UNASSIGNED_EVIDENCE_SHARDS as jt, ReviewScopeMode as k, ReviewMission as kn, MAX_FILE_EVIDENCE_CHARS as kt, gitHubPriorReviewsLayer as l, CodeReview as ln, FindingCandidate as lt, RetirableReview as m, FindingCategory as mn, MAX_UNIT_CANDIDATES as mt, GitHubApiFailure as n, assessFlatReview as nn, annotatePatch as nr, ConcernCandidate as nt, PublishedReview as o, flatAssurance as on, parsePatch as or, FileReviewReport as ot, parseGitHubSubmittedAt as p, FileSliceQuery as pn, MAX_REVIEW_CHILDREN as pt, unavailableReviewStateAuthenticatorLayer as q, PullRequestSource as qn, findingAnchorInUnitEvidence as qt, GitHubReviewTarget as r, boundedListReason as rn, commentableLines as rr, DiscoveredConcern as rt, ReviewPublisher as s, ChangedFileSummary as sn, renderReviewContent as sr, FileReviewToolkit as st, DEFAULT_GITHUB_REVIEW_AUTHOR_LOGIN as t, ReviewInputCoverage as tn, MAX_REVIEW_CONTENT_CHARS as tr, CandidateAssessment as tt, gitHubPullRequestSourceLayer as u, FileDiffQuery as un, MAX_CHILD_CONCERNS as ut, ReviewRetirementReport as v, MAX_CONCERNS as vn, ReviewPassMisbehaved as vt, MAX_STORED_UNREVIEWED_PASSES as w, REVIEW_TOOL_RESULT_MAX_BYTES as wn, fileReviewerInstructions as wt, retireStaleReviews as x, MAX_WALKTHROUGH_ENTRIES as xn, assessmentSettlesSuggestionExactly as xt, decideReviewRetirement as y, MAX_FINDINGS as yn, ReviewWorkPerspective as yt, buildProfileMission as z, makeReviewInstructions as zn, ReviewPassId as zt };
|
|
2618
2780
|
|
|
2619
|
-
//# sourceMappingURL=github-
|
|
2781
|
+
//# sourceMappingURL=github-C6jrBLA2.mjs.map
|