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.
Files changed (61) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +4 -2
  3. package/bin/cli/args.mjs +0 -1
  4. package/bin/cli/fix-formatter.mjs +2 -1
  5. package/bin/cli/formatters.mjs +0 -62
  6. package/bin/cli/help-command-pages.mjs +4 -10
  7. package/bin/cli/help-top-page.mjs +1 -1
  8. package/bin/cli/iterate-instructions.mjs +1 -1
  9. package/bin/cli/list-formatters.mjs +3 -2
  10. package/bin/cli-parser.mjs +28 -27
  11. package/bin/commands/check.mjs +6 -2
  12. package/bin/commands/commit-suggestion-instruction.mjs +3 -3
  13. package/bin/commands/iterate/classify.mjs +13 -7
  14. package/bin/commands/iterate/fix-code.mjs +2 -2
  15. package/bin/commands/iterate/index.mjs +4 -1
  16. package/bin/commands/iterate/render.mjs +3 -0
  17. package/bin/commands/iterate/stall.mjs +1 -1
  18. package/bin/commands/resolve-mutate.mjs +14 -6
  19. package/bin/commands/resolve.mjs +0 -72
  20. package/bin/comments/authors.mjs +19 -1
  21. package/bin/comments/minimize-policy.mjs +6 -5
  22. package/bin/comments/review-thread-markers.mjs +18 -0
  23. package/bin/comments/thread-visibility.mjs +5 -5
  24. package/bin/comments/visible-comments.mjs +2 -2
  25. package/bin/config/load.mjs +7 -0
  26. package/bin/config.json +15 -2
  27. package/bin/github/batch-parsers.mjs +2 -0
  28. package/bin/github/client.mjs +1 -2
  29. package/bin/github/gql/batch-pr.gql +3 -0
  30. package/bin/reporters/agent.mjs +1 -0
  31. package/bin/state/seen-comments.mjs +40 -4
  32. package/package.json +9 -5
  33. package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
  34. package/bin/checks/triage.test-support.mjs +0 -60
  35. package/bin/cli/iterate-lean.test-support.mjs +0 -3
  36. package/bin/cli-parser.clean.test-support.mjs +0 -44
  37. package/bin/cli-parser.commit-suggestion.test-support.mjs +0 -65
  38. package/bin/cli-parser.iterate-fix.test-support.mjs +0 -43
  39. package/bin/cli-parser.iterate-fixtures.mjs +0 -75
  40. package/bin/cli-parser.iterate.test-support.mjs +0 -44
  41. package/bin/cli-parser.test-support.mjs +0 -47
  42. package/bin/commands/check.test-support.mjs +0 -147
  43. package/bin/commands/clean.test-support.mjs +0 -47
  44. package/bin/commands/commit-suggestion.apply.test-support.mjs +0 -87
  45. package/bin/commands/commit-suggestion.test-support.mjs +0 -112
  46. package/bin/commands/iterate-stall.test-support.mjs +0 -24
  47. package/bin/commands/iterate-test-support.mjs +0 -150
  48. package/bin/commands/iterate-thread-test-support.mjs +0 -18
  49. package/bin/commands/iterate.fix-code-in-progress.test-support.mjs +0 -127
  50. package/bin/commands/poll.test-support.mjs +0 -77
  51. package/bin/commands/resolve-instructions.mjs +0 -59
  52. package/bin/commands/resolve.test-support.mjs +0 -116
  53. package/bin/commands/shepherd-journal.test-support.mjs +0 -7
  54. package/bin/comments/outdated.mjs +0 -15
  55. package/bin/comments/resolve.test-support.mjs +0 -44
  56. package/bin/github/batch-parsers.test-support.mjs +0 -66
  57. package/bin/github/batch.test-support.mjs +0 -66
  58. package/bin/github/client.test-support.mjs +0 -55
  59. package/bin/github/http.test-support.mjs +0 -51
  60. package/bin/state/seen-comments.test-support.mjs +0 -19
  61. 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
- }