pr-shepherd 0.23.0 → 0.25.0
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/.claude-plugin/plugin.json +1 -1
- package/README.md +4 -2
- package/bin/cli/args.mjs +0 -1
- package/bin/cli/fix-formatter.mjs +2 -1
- package/bin/cli/formatters.mjs +0 -62
- package/bin/cli/help-command-pages.mjs +4 -10
- package/bin/cli/help-top-page.mjs +1 -1
- package/bin/cli/iterate-instructions.mjs +1 -1
- package/bin/cli/list-formatters.mjs +3 -2
- package/bin/cli-parser.mjs +28 -27
- package/bin/commands/check.mjs +6 -2
- package/bin/commands/commit-suggestion-instruction.mjs +3 -3
- package/bin/commands/iterate/classify.mjs +13 -7
- package/bin/commands/iterate/fix-code.mjs +2 -2
- package/bin/commands/iterate/index.mjs +4 -1
- package/bin/commands/iterate/render.mjs +3 -0
- package/bin/commands/iterate/stall.mjs +1 -1
- package/bin/commands/resolve-mutate.mjs +14 -6
- package/bin/commands/resolve.mjs +0 -72
- package/bin/comments/authors.mjs +19 -1
- package/bin/comments/minimize-policy.mjs +6 -5
- package/bin/comments/review-thread-markers.mjs +18 -0
- package/bin/comments/thread-visibility.mjs +5 -5
- package/bin/comments/visible-comments.mjs +2 -2
- package/bin/config/load.mjs +7 -0
- package/bin/config.json +15 -2
- package/bin/github/batch-parsers.mjs +2 -0
- package/bin/github/client.mjs +1 -2
- package/bin/github/gql/batch-pr.gql +3 -0
- package/bin/reporters/agent.mjs +1 -0
- package/bin/state/seen-comments.mjs +40 -4
- package/package.json +9 -5
- package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
- package/bin/checks/triage.test-support.mjs +0 -60
- package/bin/cli/iterate-lean.test-support.mjs +0 -3
- package/bin/cli-parser.clean.test-support.mjs +0 -44
- package/bin/cli-parser.commit-suggestion.test-support.mjs +0 -65
- package/bin/cli-parser.iterate-fix.test-support.mjs +0 -43
- package/bin/cli-parser.iterate-fixtures.mjs +0 -75
- package/bin/cli-parser.iterate.test-support.mjs +0 -44
- package/bin/cli-parser.test-support.mjs +0 -47
- package/bin/commands/check.test-support.mjs +0 -147
- package/bin/commands/clean.test-support.mjs +0 -47
- package/bin/commands/commit-suggestion.apply.test-support.mjs +0 -87
- package/bin/commands/commit-suggestion.test-support.mjs +0 -112
- package/bin/commands/iterate-stall.test-support.mjs +0 -24
- package/bin/commands/iterate-test-support.mjs +0 -150
- package/bin/commands/iterate-thread-test-support.mjs +0 -18
- package/bin/commands/iterate.fix-code-in-progress.test-support.mjs +0 -127
- package/bin/commands/poll.test-support.mjs +0 -77
- package/bin/commands/resolve-instructions.mjs +0 -59
- package/bin/commands/resolve.test-support.mjs +0 -116
- package/bin/commands/shepherd-journal.test-support.mjs +0 -7
- package/bin/comments/outdated.mjs +0 -15
- package/bin/comments/resolve.test-support.mjs +0 -44
- package/bin/github/batch-parsers.test-support.mjs +0 -66
- package/bin/github/batch.test-support.mjs +0 -66
- package/bin/github/client.test-support.mjs +0 -55
- package/bin/github/http.test-support.mjs +0 -51
- package/bin/state/seen-comments.test-support.mjs +0 -19
- package/bin/suggestions/patch.test-support.mjs +0 -2
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import { vi, beforeEach } from "vitest";
|
|
2
|
-
vi.mock("../github/batch.mts", () => ({ fetchPrBatch: vi.fn() }));
|
|
3
|
-
vi.mock("../github/client.mts", () => ({
|
|
4
|
-
getRepoInfo: vi.fn().mockResolvedValue({ owner: "owner", name: "repo" }),
|
|
5
|
-
getCurrentPrNumber: vi.fn().mockResolvedValue(42),
|
|
6
|
-
getMergeableState: vi.fn(),
|
|
7
|
-
}));
|
|
8
|
-
vi.mock("../checks/triage.mts", () => ({
|
|
9
|
-
triageFailingChecks: vi.fn((checks) => Promise.resolve(checks)),
|
|
10
|
-
fetchStartupFailureChecks: vi.fn().mockResolvedValue([]),
|
|
11
|
-
}));
|
|
12
|
-
vi.mock("../github/check-annotations.mts", () => ({
|
|
13
|
-
fetchCheckRunAnnotations: vi.fn().mockResolvedValue([]),
|
|
14
|
-
}));
|
|
15
|
-
vi.mock("../comments/resolve.mts", () => ({
|
|
16
|
-
autoResolveOutdated: vi.fn().mockResolvedValue({ resolved: [], errors: [] }),
|
|
17
|
-
}));
|
|
18
|
-
vi.mock("../state/seen-comments.mts", async (importOriginal) => {
|
|
19
|
-
const actual = await importOriginal();
|
|
20
|
-
return {
|
|
21
|
-
...actual,
|
|
22
|
-
loadSeenMap: vi.fn().mockResolvedValue(new Map()),
|
|
23
|
-
markSeen: vi.fn().mockResolvedValue(undefined),
|
|
24
|
-
};
|
|
25
|
-
});
|
|
26
|
-
const { mockLoadConfig } = vi.hoisted(() => ({ mockLoadConfig: vi.fn() }));
|
|
27
|
-
vi.mock("../config/load.mts", () => ({ loadConfig: mockLoadConfig }));
|
|
28
|
-
import { runCheck } from "./check.mjs";
|
|
29
|
-
import { fetchPrBatch } from "../github/batch.mjs";
|
|
30
|
-
import { getCurrentPrNumber, getMergeableState } from "../github/client.mjs";
|
|
31
|
-
import { fetchStartupFailureChecks, triageFailingChecks } from "../checks/triage.mjs";
|
|
32
|
-
import { fetchCheckRunAnnotations } from "../github/check-annotations.mjs";
|
|
33
|
-
import { loadSeenMap, markSeen, hashBody } from "../state/seen-comments.mjs";
|
|
34
|
-
import { autoResolveOutdated } from "../comments/resolve.mjs";
|
|
35
|
-
const mockFetchPrBatch = vi.mocked(fetchPrBatch);
|
|
36
|
-
const mockGetCurrentPrNumber = vi.mocked(getCurrentPrNumber);
|
|
37
|
-
const mockGetMergeableState = vi.mocked(getMergeableState);
|
|
38
|
-
const mockTriageFailingChecks = vi.mocked(triageFailingChecks);
|
|
39
|
-
const mockFetchStartupFailureChecks = vi.mocked(fetchStartupFailureChecks);
|
|
40
|
-
const mockFetchCheckRunAnnotations = vi.mocked(fetchCheckRunAnnotations);
|
|
41
|
-
const mockLoadSeenMap = vi.mocked(loadSeenMap);
|
|
42
|
-
const mockMarkSeen = vi.mocked(markSeen);
|
|
43
|
-
const mockAutoResolveOutdated = vi.mocked(autoResolveOutdated);
|
|
44
|
-
const BASE_OPTS = { format: "text" };
|
|
45
|
-
function defaultConfig() {
|
|
46
|
-
return {
|
|
47
|
-
iterate: {
|
|
48
|
-
fixAttemptsPerThread: 3,
|
|
49
|
-
stallTimeoutMinutes: 60,
|
|
50
|
-
minimizeApprovals: false,
|
|
51
|
-
minimizeComments: "all",
|
|
52
|
-
},
|
|
53
|
-
watch: { readyDelayMinutes: 10 },
|
|
54
|
-
resolve: {
|
|
55
|
-
shaPoll: { intervalMs: 2000, maxAttempts: 10 },
|
|
56
|
-
fetchReviewSummaries: true,
|
|
57
|
-
},
|
|
58
|
-
checks: {
|
|
59
|
-
ciTriggerEvents: ["pull_request", "pull_request_target"],
|
|
60
|
-
},
|
|
61
|
-
mergeStatus: { blockingReviewerLogins: ["copilot"] },
|
|
62
|
-
actions: {
|
|
63
|
-
autoResolveOutdated: true,
|
|
64
|
-
autoMarkReady: true,
|
|
65
|
-
commitSuggestions: true,
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
function makeCheck(overrides = {}) {
|
|
70
|
-
return {
|
|
71
|
-
id: null,
|
|
72
|
-
name: "tests",
|
|
73
|
-
status: "COMPLETED",
|
|
74
|
-
conclusion: "SUCCESS",
|
|
75
|
-
detailsUrl: "",
|
|
76
|
-
event: "pull_request",
|
|
77
|
-
runId: null,
|
|
78
|
-
category: "passed",
|
|
79
|
-
...overrides,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
function makeBatchData(overrides = {}) {
|
|
83
|
-
return {
|
|
84
|
-
nodeId: "PR_kgDOAAA",
|
|
85
|
-
number: 42,
|
|
86
|
-
state: "OPEN",
|
|
87
|
-
isDraft: false,
|
|
88
|
-
mergeable: "MERGEABLE",
|
|
89
|
-
mergeStateStatus: "CLEAN",
|
|
90
|
-
reviewDecision: "APPROVED",
|
|
91
|
-
headRefOid: "abc123",
|
|
92
|
-
headRefName: "feature",
|
|
93
|
-
headRepoWithOwner: "owner/repo",
|
|
94
|
-
baseRefName: "main",
|
|
95
|
-
reviewRequests: [],
|
|
96
|
-
latestReviews: [],
|
|
97
|
-
reviewThreads: [],
|
|
98
|
-
comments: [],
|
|
99
|
-
changesRequestedReviews: [],
|
|
100
|
-
reviewSummaries: [],
|
|
101
|
-
approvedReviews: [],
|
|
102
|
-
branchProtection: null,
|
|
103
|
-
checks: [makeCheck()],
|
|
104
|
-
...overrides,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
function makeThread(overrides = {}) {
|
|
108
|
-
return {
|
|
109
|
-
id: "t1",
|
|
110
|
-
isResolved: false,
|
|
111
|
-
isOutdated: false,
|
|
112
|
-
isMinimized: false,
|
|
113
|
-
path: "src/foo.mts",
|
|
114
|
-
line: 10,
|
|
115
|
-
startLine: null,
|
|
116
|
-
author: "reviewer",
|
|
117
|
-
authorType: "Unknown",
|
|
118
|
-
body: "fix this",
|
|
119
|
-
url: "",
|
|
120
|
-
createdAtUnix: 0,
|
|
121
|
-
...overrides,
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
function makeComment(overrides = {}) {
|
|
125
|
-
return {
|
|
126
|
-
id: "c1",
|
|
127
|
-
author: "commenter",
|
|
128
|
-
authorType: "Unknown",
|
|
129
|
-
body: "comment body",
|
|
130
|
-
url: "",
|
|
131
|
-
createdAtUnix: 0,
|
|
132
|
-
isMinimized: false,
|
|
133
|
-
...overrides,
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
// No PR found
|
|
137
|
-
export function registerHooks() {
|
|
138
|
-
beforeEach(() => {
|
|
139
|
-
vi.clearAllMocks();
|
|
140
|
-
mockLoadConfig.mockReturnValue(defaultConfig());
|
|
141
|
-
mockFetchPrBatch.mockResolvedValue({ data: makeBatchData() });
|
|
142
|
-
mockGetMergeableState.mockResolvedValue({ mergeable: "MERGEABLE", mergeStateStatus: "CLEAN" });
|
|
143
|
-
mockFetchStartupFailureChecks.mockResolvedValue([]);
|
|
144
|
-
mockFetchCheckRunAnnotations.mockResolvedValue([]);
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
export { BASE_OPTS, autoResolveOutdated, defaultConfig, fetchPrBatch, fetchStartupFailureChecks, fetchCheckRunAnnotations, getCurrentPrNumber, getMergeableState, hashBody, loadSeenMap, makeBatchData, makeCheck, makeComment, makeThread, markSeen, mockAutoResolveOutdated, mockFetchPrBatch, mockFetchStartupFailureChecks, mockFetchCheckRunAnnotations, mockGetCurrentPrNumber, mockGetMergeableState, mockLoadConfig, mockLoadSeenMap, mockMarkSeen, mockTriageFailingChecks, runCheck, triageFailingChecks, };
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { mkdtemp, realpath, rm, mkdir, writeFile, stat } from "node:fs/promises";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
vi.mock("../github/client.mts", () => ({
|
|
6
|
-
getRepoInfo: vi.fn().mockResolvedValue({ owner: "acme", name: "widgets" }),
|
|
7
|
-
getCurrentBranch: vi.fn().mockResolvedValue("feature/test"),
|
|
8
|
-
getCurrentPrNumber: vi.fn().mockResolvedValue(42),
|
|
9
|
-
getPrNumberForBranch: vi.fn().mockResolvedValue(42),
|
|
10
|
-
}));
|
|
11
|
-
import { getRepoInfo, getCurrentBranch, getCurrentPrNumber, getPrNumberForBranch, } from "../github/client.mjs";
|
|
12
|
-
export const mockGetRepoInfo = vi.mocked(getRepoInfo);
|
|
13
|
-
export const mockGetCurrentBranch = vi.mocked(getCurrentBranch);
|
|
14
|
-
export const mockGetCurrentPrNumber = vi.mocked(getCurrentPrNumber);
|
|
15
|
-
export const mockGetPrNumberForBranch = vi.mocked(getPrNumberForBranch);
|
|
16
|
-
export let stateDir;
|
|
17
|
-
export function registerHooks() {
|
|
18
|
-
beforeEach(async () => {
|
|
19
|
-
const tmpPath = await mkdtemp(join(tmpdir(), "shepherd-clean-test-"));
|
|
20
|
-
stateDir = await realpath(tmpPath);
|
|
21
|
-
process.env["PR_SHEPHERD_STATE_DIR"] = stateDir;
|
|
22
|
-
vi.clearAllMocks();
|
|
23
|
-
mockGetRepoInfo.mockResolvedValue({ owner: "acme", name: "widgets" });
|
|
24
|
-
mockGetCurrentBranch.mockResolvedValue("feature/test");
|
|
25
|
-
mockGetCurrentPrNumber.mockResolvedValue(42);
|
|
26
|
-
mockGetPrNumberForBranch.mockResolvedValue(42);
|
|
27
|
-
});
|
|
28
|
-
afterEach(async () => {
|
|
29
|
-
delete process.env["PR_SHEPHERD_STATE_DIR"];
|
|
30
|
-
await rm(stateDir, { recursive: true, force: true });
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
export async function seedPrDir(dir, pr) {
|
|
34
|
-
const prDir = join(dir, "acme-widgets", String(pr));
|
|
35
|
-
await mkdir(join(prDir, "seen"), { recursive: true });
|
|
36
|
-
await writeFile(join(prDir, "fix-attempts.json"), "{}", "utf8");
|
|
37
|
-
return prDir;
|
|
38
|
-
}
|
|
39
|
-
export async function pathExists(p) {
|
|
40
|
-
try {
|
|
41
|
-
await stat(p);
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { vi } from "vitest";
|
|
2
|
-
// ---------------------------------------------------------------------------
|
|
3
|
-
// Hoisted mocks
|
|
4
|
-
// ---------------------------------------------------------------------------
|
|
5
|
-
const { mockExecFile } = vi.hoisted(() => ({ mockExecFile: vi.fn() }));
|
|
6
|
-
vi.mock("node:child_process", () => ({
|
|
7
|
-
execFile: (cmd, args, optsOrCb, maybeCb) => {
|
|
8
|
-
const cb = typeof optsOrCb === "function" ? optsOrCb : maybeCb;
|
|
9
|
-
mockExecFile(cmd, args)
|
|
10
|
-
.then((result) => cb(null, result))
|
|
11
|
-
.catch((err) => cb(err, { stdout: "", stderr: err.stderr ?? "" }));
|
|
12
|
-
},
|
|
13
|
-
}));
|
|
14
|
-
vi.mock("node:fs/promises", () => ({
|
|
15
|
-
readFile: vi.fn(),
|
|
16
|
-
}));
|
|
17
|
-
vi.mock("../github/client.mts", () => ({
|
|
18
|
-
getRepoInfo: vi.fn().mockResolvedValue({ owner: "owner", name: "repo" }),
|
|
19
|
-
getCurrentPrNumber: vi.fn().mockResolvedValue(42),
|
|
20
|
-
getCurrentBranch: vi.fn().mockResolvedValue("feature/foo"),
|
|
21
|
-
}));
|
|
22
|
-
vi.mock("../github/batch.mts", () => ({
|
|
23
|
-
fetchPrBatch: vi.fn(),
|
|
24
|
-
}));
|
|
25
|
-
import { runCommitSuggestion } from "./commit-suggestion.mjs";
|
|
26
|
-
import { getCurrentBranch } from "../github/client.mjs";
|
|
27
|
-
import { fetchPrBatch } from "../github/batch.mjs";
|
|
28
|
-
import { readFile } from "node:fs/promises";
|
|
29
|
-
const mockGetCurrentBranch = vi.mocked(getCurrentBranch);
|
|
30
|
-
const mockFetchBatch = vi.mocked(fetchPrBatch);
|
|
31
|
-
const mockReadFile = vi.mocked(readFile);
|
|
32
|
-
function makeThread(overrides = {}) {
|
|
33
|
-
return {
|
|
34
|
-
id: "PRRT_x",
|
|
35
|
-
isResolved: false,
|
|
36
|
-
isOutdated: false,
|
|
37
|
-
isMinimized: false,
|
|
38
|
-
path: "src/foo.ts",
|
|
39
|
-
line: 5,
|
|
40
|
-
startLine: null,
|
|
41
|
-
author: "alice",
|
|
42
|
-
authorType: "Unknown",
|
|
43
|
-
body: "Use a const here.\n\n```suggestion\nconst x = 10;\n```",
|
|
44
|
-
url: "",
|
|
45
|
-
createdAtUnix: 0,
|
|
46
|
-
...overrides,
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
function makeBatch(threads) {
|
|
50
|
-
return {
|
|
51
|
-
nodeId: "PR_kgDOAAA",
|
|
52
|
-
number: 42,
|
|
53
|
-
state: "OPEN",
|
|
54
|
-
isDraft: false,
|
|
55
|
-
mergeable: "MERGEABLE",
|
|
56
|
-
mergeStateStatus: "CLEAN",
|
|
57
|
-
reviewDecision: "APPROVED",
|
|
58
|
-
headRefOid: "headsha",
|
|
59
|
-
headRefName: "feature/foo",
|
|
60
|
-
headRepoWithOwner: "owner/repo",
|
|
61
|
-
baseRefName: "main",
|
|
62
|
-
reviewRequests: [],
|
|
63
|
-
latestReviews: [],
|
|
64
|
-
reviewThreads: threads,
|
|
65
|
-
checks: [],
|
|
66
|
-
comments: [],
|
|
67
|
-
changesRequestedReviews: [],
|
|
68
|
-
reviewSummaries: [],
|
|
69
|
-
approvedReviews: [],
|
|
70
|
-
branchProtection: null,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
const FILE_CONTENT = "line1\n" +
|
|
74
|
-
"line2\n" +
|
|
75
|
-
"line3\n" +
|
|
76
|
-
"line4\n" +
|
|
77
|
-
"const x = 1;\n" + // line 5 — matches the suggestion anchor
|
|
78
|
-
"line6\n" +
|
|
79
|
-
"line7\n";
|
|
80
|
-
const GLOBAL_OPTS = { format: "text" };
|
|
81
|
-
function makeGitSuccess(stdout = "") {
|
|
82
|
-
return Promise.resolve({ stdout, stderr: "" });
|
|
83
|
-
}
|
|
84
|
-
// ---------------------------------------------------------------------------
|
|
85
|
-
// Output shape and instruction content
|
|
86
|
-
// ---------------------------------------------------------------------------
|
|
87
|
-
export { FILE_CONTENT, GLOBAL_OPTS, fetchPrBatch, getCurrentBranch, makeBatch, makeGitSuccess, makeThread, mockExecFile, mockFetchBatch, mockGetCurrentBranch, mockReadFile, readFile, runCommitSuggestion, };
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { vi, beforeEach } from "vitest";
|
|
2
|
-
// ---------------------------------------------------------------------------
|
|
3
|
-
// Hoisted mocks
|
|
4
|
-
// ---------------------------------------------------------------------------
|
|
5
|
-
const { mockExecFile } = vi.hoisted(() => ({ mockExecFile: vi.fn() }));
|
|
6
|
-
vi.mock("node:child_process", () => ({
|
|
7
|
-
execFile: (cmd, args, optsOrCb, maybeCb) => {
|
|
8
|
-
const cb = typeof optsOrCb === "function" ? optsOrCb : maybeCb;
|
|
9
|
-
mockExecFile(cmd, args)
|
|
10
|
-
.then((result) => cb(null, result))
|
|
11
|
-
.catch((err) => cb(err, { stdout: "", stderr: err.stderr ?? "" }));
|
|
12
|
-
},
|
|
13
|
-
}));
|
|
14
|
-
vi.mock("node:fs/promises", () => ({
|
|
15
|
-
readFile: vi.fn(),
|
|
16
|
-
}));
|
|
17
|
-
vi.mock("../github/client.mts", () => ({
|
|
18
|
-
getRepoInfo: vi.fn().mockResolvedValue({ owner: "owner", name: "repo" }),
|
|
19
|
-
getCurrentPrNumber: vi.fn().mockResolvedValue(42),
|
|
20
|
-
getCurrentBranch: vi.fn().mockResolvedValue("feature/foo"),
|
|
21
|
-
}));
|
|
22
|
-
vi.mock("../github/batch.mts", () => ({
|
|
23
|
-
fetchPrBatch: vi.fn(),
|
|
24
|
-
}));
|
|
25
|
-
const { mockLoadConfig } = vi.hoisted(() => ({ mockLoadConfig: vi.fn() }));
|
|
26
|
-
vi.mock("../config/load.mts", () => ({
|
|
27
|
-
loadConfig: mockLoadConfig,
|
|
28
|
-
}));
|
|
29
|
-
import { runCommitSuggestion } from "./commit-suggestion.mjs";
|
|
30
|
-
import { getCurrentBranch, getCurrentPrNumber } from "../github/client.mjs";
|
|
31
|
-
import { fetchPrBatch } from "../github/batch.mjs";
|
|
32
|
-
import { readFile } from "node:fs/promises";
|
|
33
|
-
const mockGetCurrentBranch = vi.mocked(getCurrentBranch);
|
|
34
|
-
const mockGetCurrentPrNumber = vi.mocked(getCurrentPrNumber);
|
|
35
|
-
const mockFetchBatch = vi.mocked(fetchPrBatch);
|
|
36
|
-
const mockReadFile = vi.mocked(readFile);
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
// Fixtures
|
|
39
|
-
// ---------------------------------------------------------------------------
|
|
40
|
-
function makeThread(overrides = {}) {
|
|
41
|
-
return {
|
|
42
|
-
id: "PRRT_x",
|
|
43
|
-
isResolved: false,
|
|
44
|
-
isOutdated: false,
|
|
45
|
-
isMinimized: false,
|
|
46
|
-
path: "src/foo.ts",
|
|
47
|
-
line: 5,
|
|
48
|
-
startLine: null,
|
|
49
|
-
author: "alice",
|
|
50
|
-
authorType: "Unknown",
|
|
51
|
-
body: "Use a const here.\n\n```suggestion\nconst x = 10;\n```",
|
|
52
|
-
url: "",
|
|
53
|
-
createdAtUnix: 0,
|
|
54
|
-
...overrides,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
function makeBatch(threads, headRepoWithOwner = "owner/repo") {
|
|
58
|
-
return {
|
|
59
|
-
nodeId: "PR_kgDOAAA",
|
|
60
|
-
number: 42,
|
|
61
|
-
state: "OPEN",
|
|
62
|
-
isDraft: false,
|
|
63
|
-
mergeable: "MERGEABLE",
|
|
64
|
-
mergeStateStatus: "CLEAN",
|
|
65
|
-
reviewDecision: "APPROVED",
|
|
66
|
-
headRefOid: "headsha",
|
|
67
|
-
headRefName: "feature/foo",
|
|
68
|
-
headRepoWithOwner,
|
|
69
|
-
baseRefName: "main",
|
|
70
|
-
reviewRequests: [],
|
|
71
|
-
latestReviews: [],
|
|
72
|
-
reviewThreads: threads,
|
|
73
|
-
checks: [],
|
|
74
|
-
comments: [],
|
|
75
|
-
changesRequestedReviews: [],
|
|
76
|
-
reviewSummaries: [],
|
|
77
|
-
approvedReviews: [],
|
|
78
|
-
branchProtection: null,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
const FILE_CONTENT = "line1\n" +
|
|
82
|
-
"line2\n" +
|
|
83
|
-
"line3\n" +
|
|
84
|
-
"line4\n" +
|
|
85
|
-
"const x = 1;\n" + // line 5 — matches the suggestion anchor
|
|
86
|
-
"line6\n" +
|
|
87
|
-
"line7\n";
|
|
88
|
-
const GLOBAL_OPTS = { format: "text" };
|
|
89
|
-
function makeGitSuccess(stdout = "") {
|
|
90
|
-
return Promise.resolve({ stdout, stderr: "" });
|
|
91
|
-
}
|
|
92
|
-
function setupHappyPath() {
|
|
93
|
-
mockFetchBatch.mockResolvedValue({ data: makeBatch([makeThread()]) });
|
|
94
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
95
|
-
mockReadFile.mockResolvedValue(FILE_CONTENT);
|
|
96
|
-
mockExecFile.mockImplementation((cmd, args) => {
|
|
97
|
-
if (cmd === "git" && args[0] === "rev-parse")
|
|
98
|
-
return makeGitSuccess("headsha\n");
|
|
99
|
-
if (cmd === "git" && args[0] === "status")
|
|
100
|
-
return makeGitSuccess(""); // file is clean
|
|
101
|
-
throw new Error(`Unexpected execFile call: ${cmd} ${args.join(" ")}`);
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
// ---------------------------------------------------------------------------
|
|
105
|
-
// Validation
|
|
106
|
-
// ---------------------------------------------------------------------------
|
|
107
|
-
export function registerHooks() {
|
|
108
|
-
beforeEach(() => {
|
|
109
|
-
mockLoadConfig.mockReturnValue({});
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
export { FILE_CONTENT, GLOBAL_OPTS, fetchPrBatch, getCurrentBranch, getCurrentPrNumber, makeBatch, makeGitSuccess, makeThread, mockExecFile, mockFetchBatch, mockGetCurrentBranch, mockGetCurrentPrNumber, mockLoadConfig, mockReadFile, readFile, runCommitSuggestion, setupHappyPath, };
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { vi } from "vitest";
|
|
2
|
-
import { NOW, makeOpts } from "./iterate-test-support.mjs";
|
|
3
|
-
import { readStallState, writeStallState } from "../state/iterate-stall.mjs";
|
|
4
|
-
const mockReadStallState = vi.mocked(readStallState);
|
|
5
|
-
const mockWriteStallState = vi.mocked(writeStallState);
|
|
6
|
-
const STALL_TIMEOUT_S = 1800;
|
|
7
|
-
const RESOLUTION_ONLY_THREAD = {
|
|
8
|
-
id: "thread-resolution-only",
|
|
9
|
-
isResolved: false,
|
|
10
|
-
isOutdated: true,
|
|
11
|
-
isMinimized: false,
|
|
12
|
-
path: "src/old.mts",
|
|
13
|
-
line: null,
|
|
14
|
-
startLine: null,
|
|
15
|
-
author: "reviewer",
|
|
16
|
-
authorType: "Unknown",
|
|
17
|
-
body: "Already addressed on an old diff",
|
|
18
|
-
url: "",
|
|
19
|
-
createdAtUnix: NOW - 3600,
|
|
20
|
-
};
|
|
21
|
-
function makeOpts30mStall(overrides = {}) {
|
|
22
|
-
return makeOpts({ stallTimeoutSeconds: STALL_TIMEOUT_S, noAutoMarkReady: true, ...overrides });
|
|
23
|
-
}
|
|
24
|
-
export { RESOLUTION_ONLY_THREAD, STALL_TIMEOUT_S, makeOpts30mStall, mockReadStallState, mockWriteStallState, };
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
const mockFetch = vi.fn();
|
|
3
|
-
vi.stubGlobal("fetch", mockFetch);
|
|
4
|
-
const { mockExecFile } = vi.hoisted(() => ({ mockExecFile: vi.fn() }));
|
|
5
|
-
vi.mock("node:child_process", () => ({
|
|
6
|
-
execFile: (cmd, args, optsOrCb, maybeCb) => {
|
|
7
|
-
const cb = typeof optsOrCb === "function" ? optsOrCb : maybeCb;
|
|
8
|
-
mockExecFile(cmd, args)
|
|
9
|
-
.then((result) => cb(null, result))
|
|
10
|
-
.catch((err) => cb(err, { stdout: "", stderr: err.stderr ?? "" }));
|
|
11
|
-
},
|
|
12
|
-
}));
|
|
13
|
-
vi.mock("./check.mts", () => ({ runCheck: vi.fn() }));
|
|
14
|
-
vi.mock("./ready-delay.mts", () => ({ updateReadyDelay: vi.fn() }));
|
|
15
|
-
vi.mock("../github/client.mts", () => ({ getCurrentPrNumber: vi.fn().mockResolvedValue(42) }));
|
|
16
|
-
vi.mock("../state/fix-attempts.mts", () => ({
|
|
17
|
-
readFixAttempts: vi.fn().mockResolvedValue(null),
|
|
18
|
-
writeFixAttempts: vi.fn().mockResolvedValue(undefined),
|
|
19
|
-
}));
|
|
20
|
-
vi.mock("../state/iterate-stall.mts", () => ({
|
|
21
|
-
readStallState: vi.fn().mockResolvedValue(null),
|
|
22
|
-
writeStallState: vi.fn().mockResolvedValue(undefined),
|
|
23
|
-
clearStallState: vi.fn().mockResolvedValue(undefined),
|
|
24
|
-
}));
|
|
25
|
-
const { mockLoadConfig } = vi.hoisted(() => ({ mockLoadConfig: vi.fn() }));
|
|
26
|
-
vi.mock("../config/load.mts", () => ({ loadConfig: mockLoadConfig }));
|
|
27
|
-
import { runCheck } from "./check.mjs";
|
|
28
|
-
import { updateReadyDelay } from "./ready-delay.mjs";
|
|
29
|
-
import { getCurrentPrNumber } from "../github/client.mjs";
|
|
30
|
-
import { readFixAttempts, writeFixAttempts } from "../state/fix-attempts.mjs";
|
|
31
|
-
import { clearStallState, readStallState, writeStallState } from "../state/iterate-stall.mjs";
|
|
32
|
-
import { buildEscalateHumanMessage, buildEscalateSuggestion, checkEscalateTriggers, } from "./iterate/escalate.mjs";
|
|
33
|
-
import { buildRelevantChecks, buildWaitLog, getCurrentHeadSha } from "./iterate/helpers.mjs";
|
|
34
|
-
const mockRunCheck = vi.mocked(runCheck);
|
|
35
|
-
const mockUpdateReadyDelay = vi.mocked(updateReadyDelay);
|
|
36
|
-
const mockGetCurrentPrNumber = vi.mocked(getCurrentPrNumber);
|
|
37
|
-
const mockReadFixAttempts = vi.mocked(readFixAttempts);
|
|
38
|
-
const mockWriteFixAttempts = vi.mocked(writeFixAttempts);
|
|
39
|
-
const mockReadStallState = vi.mocked(readStallState);
|
|
40
|
-
const mockWriteStallState = vi.mocked(writeStallState);
|
|
41
|
-
const mockClearStallState = vi.mocked(clearStallState);
|
|
42
|
-
const NOW = 1_700_000_000;
|
|
43
|
-
const READY_STATE_DEFAULT = { isReady: true, shouldCancel: false, remainingSeconds: 300 };
|
|
44
|
-
function makeReport(overrides = {}) {
|
|
45
|
-
return {
|
|
46
|
-
pr: 42,
|
|
47
|
-
nodeId: "PR_kgDOAAA",
|
|
48
|
-
repo: "owner/repo",
|
|
49
|
-
status: "READY",
|
|
50
|
-
baseBranch: "main",
|
|
51
|
-
mergeStatus: {
|
|
52
|
-
status: "CLEAN",
|
|
53
|
-
state: "OPEN",
|
|
54
|
-
isDraft: false,
|
|
55
|
-
mergeable: "MERGEABLE",
|
|
56
|
-
reviewDecision: "APPROVED",
|
|
57
|
-
blockingBotReviewInProgress: false,
|
|
58
|
-
mergeStateStatus: "CLEAN",
|
|
59
|
-
},
|
|
60
|
-
checks: {
|
|
61
|
-
passing: [
|
|
62
|
-
{
|
|
63
|
-
name: "ci",
|
|
64
|
-
status: "COMPLETED",
|
|
65
|
-
conclusion: "SUCCESS",
|
|
66
|
-
detailsUrl: "https://github.com/owner/repo/actions/runs/1",
|
|
67
|
-
event: "pull_request",
|
|
68
|
-
runId: "run-1",
|
|
69
|
-
category: "passed",
|
|
70
|
-
},
|
|
71
|
-
],
|
|
72
|
-
failing: [],
|
|
73
|
-
inProgress: [],
|
|
74
|
-
skipped: [],
|
|
75
|
-
filtered: [],
|
|
76
|
-
filteredNames: [],
|
|
77
|
-
blockedByFilteredCheck: false,
|
|
78
|
-
},
|
|
79
|
-
threads: {
|
|
80
|
-
actionable: [],
|
|
81
|
-
resolutionOnly: [],
|
|
82
|
-
autoResolved: [],
|
|
83
|
-
autoResolveErrors: [],
|
|
84
|
-
firstLook: [],
|
|
85
|
-
},
|
|
86
|
-
comments: { actionable: [], firstLook: [] },
|
|
87
|
-
changesRequestedReviews: [],
|
|
88
|
-
reviewSummaries: [],
|
|
89
|
-
firstLookSummaries: [],
|
|
90
|
-
editedSummaries: [],
|
|
91
|
-
approvedReviews: [],
|
|
92
|
-
branchProtection: null,
|
|
93
|
-
...overrides,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
function makeOpts(overrides = {}) {
|
|
97
|
-
return { prNumber: 42, format: "json", readyDelaySeconds: 600, ...overrides };
|
|
98
|
-
}
|
|
99
|
-
function makeReview(id, author, body) {
|
|
100
|
-
return { id, author, authorType: "Unknown", body };
|
|
101
|
-
}
|
|
102
|
-
function defaultConfig() {
|
|
103
|
-
return {
|
|
104
|
-
iterate: {
|
|
105
|
-
fixAttemptsPerThread: 3,
|
|
106
|
-
stallTimeoutMinutes: 60,
|
|
107
|
-
minimizeApprovals: false,
|
|
108
|
-
minimizeComments: "all",
|
|
109
|
-
},
|
|
110
|
-
watch: { readyDelayMinutes: 10 },
|
|
111
|
-
resolve: {
|
|
112
|
-
concurrency: 4,
|
|
113
|
-
shaPoll: { intervalMs: 2000, maxAttempts: 10 },
|
|
114
|
-
fetchReviewSummaries: true,
|
|
115
|
-
},
|
|
116
|
-
checks: { ciTriggerEvents: ["pull_request", "pull_request_target"] },
|
|
117
|
-
mergeStatus: { blockingReviewerLogins: ["copilot"] },
|
|
118
|
-
actions: { autoResolveOutdated: true, autoMarkReady: true, commitSuggestions: true },
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
function registerIterateHooks(config = defaultConfig) {
|
|
122
|
-
beforeEach(() => {
|
|
123
|
-
vi.clearAllMocks();
|
|
124
|
-
mockLoadConfig.mockReturnValue(config());
|
|
125
|
-
process.env["GH_TOKEN"] = "test-token";
|
|
126
|
-
mockExecFile.mockImplementation((cmd, args) => {
|
|
127
|
-
if (cmd === "git" && args[0] === "rev-parse") {
|
|
128
|
-
return Promise.resolve({ stdout: "abc123", stderr: "" });
|
|
129
|
-
}
|
|
130
|
-
return Promise.resolve({ stdout: "", stderr: "" });
|
|
131
|
-
});
|
|
132
|
-
mockFetch.mockResolvedValue({
|
|
133
|
-
ok: true,
|
|
134
|
-
status: 200,
|
|
135
|
-
headers: new Headers({ "content-type": "application/json" }),
|
|
136
|
-
json: () => Promise.resolve({ data: {} }),
|
|
137
|
-
text: () => Promise.resolve('{"data":{}}'),
|
|
138
|
-
});
|
|
139
|
-
vi.useFakeTimers();
|
|
140
|
-
vi.setSystemTime(NOW * 1000);
|
|
141
|
-
mockUpdateReadyDelay.mockResolvedValue(READY_STATE_DEFAULT);
|
|
142
|
-
mockReadFixAttempts.mockResolvedValue(null);
|
|
143
|
-
mockWriteFixAttempts.mockResolvedValue(undefined);
|
|
144
|
-
mockReadStallState.mockResolvedValue(null);
|
|
145
|
-
mockWriteStallState.mockResolvedValue(undefined);
|
|
146
|
-
mockClearStallState.mockResolvedValue(undefined);
|
|
147
|
-
});
|
|
148
|
-
afterEach(() => vi.useRealTimers());
|
|
149
|
-
}
|
|
150
|
-
export { NOW, buildEscalateHumanMessage, buildEscalateSuggestion, buildRelevantChecks, buildWaitLog, checkEscalateTriggers, defaultConfig, getCurrentHeadSha, makeOpts, makeReport, makeReview, mockExecFile, mockFetch, mockGetCurrentPrNumber, mockClearStallState, mockLoadConfig, mockReadFixAttempts, mockReadStallState, mockRunCheck, mockUpdateReadyDelay, mockWriteFixAttempts, mockWriteStallState, registerIterateHooks, };
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { NOW } from "./iterate-test-support.mjs";
|
|
2
|
-
export function makeThread(overrides = {}) {
|
|
3
|
-
return {
|
|
4
|
-
id: "thread-1",
|
|
5
|
-
isResolved: false,
|
|
6
|
-
isOutdated: false,
|
|
7
|
-
isMinimized: false,
|
|
8
|
-
path: "src/foo.mts",
|
|
9
|
-
line: 10,
|
|
10
|
-
startLine: null,
|
|
11
|
-
author: "reviewer",
|
|
12
|
-
authorType: "Unknown",
|
|
13
|
-
body: "Fix this",
|
|
14
|
-
url: "",
|
|
15
|
-
createdAtUnix: NOW - 3600,
|
|
16
|
-
...overrides,
|
|
17
|
-
};
|
|
18
|
-
}
|