pr-shepherd 0.23.0 → 0.24.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 +2 -1
- package/bin/cli/fix-formatter.mjs +2 -1
- package/bin/cli/iterate-instructions.mjs +1 -1
- package/bin/cli/list-formatters.mjs +3 -2
- package/bin/commands/check.mjs +6 -2
- 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-instructions.mjs +5 -2
- package/bin/commands/resolve-mutate.mjs +14 -6
- package/bin/commands/resolve.mjs +7 -3
- 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 +14 -0
- 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 +8 -4
- 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.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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pr-shepherd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Autonomous PR CI monitor and review-comment resolver for agentic coding tools",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Jonathan Ong",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"@types/node": "^25.6.0",
|
|
29
29
|
"@vitest/coverage-v8": "^4.1.4",
|
|
30
30
|
"husky": "^9.1.7",
|
|
31
|
+
"knip": "^5.88.1",
|
|
31
32
|
"oxfmt": "^0.48.0",
|
|
32
33
|
"oxlint": "^1.60.0",
|
|
33
34
|
"typescript": "^6.0.3",
|
|
@@ -38,9 +39,12 @@
|
|
|
38
39
|
"prepare": "node scripts/install-husky.mjs && npm run build",
|
|
39
40
|
"prepublishOnly": "npm run typecheck && npm test && npm run build",
|
|
40
41
|
"typecheck": "tsc --noEmit",
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
42
|
+
"knip": "knip",
|
|
43
|
+
"knip:production": "knip --production",
|
|
44
|
+
"lint:dead-code": "npm run --silent knip && npm run --silent knip:production",
|
|
45
|
+
"lint": "oxlint src/ test-helpers/ fixtures/*.mts test-cases/ plugins/ .agents/plugins/ && npm run --silent lint:dead-code",
|
|
46
|
+
"format": "oxfmt src/ test-helpers/ fixtures/*.mts test-cases/*.mts plugins/ .agents/plugins/ docs/ README.md",
|
|
47
|
+
"format:check": "oxfmt --check src/ test-helpers/ fixtures/*.mts test-cases/*.mts plugins/ .agents/plugins/ docs/ README.md",
|
|
44
48
|
"test": "vitest run",
|
|
45
49
|
"test:coverage": "vitest run --coverage && node scripts/strip-lcov-branches.mjs",
|
|
46
50
|
"test:watch": "vitest"
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { vi, beforeEach } from "vitest";
|
|
2
|
-
// ---------------------------------------------------------------------------
|
|
3
|
-
// Stub fetch globally so http.mts uses our mock.
|
|
4
|
-
// ---------------------------------------------------------------------------
|
|
5
|
-
const mockFetch = vi.fn();
|
|
6
|
-
vi.stubGlobal("fetch", mockFetch);
|
|
7
|
-
import { fetchStartupFailureChecks, triageFailingChecks } from "./triage.mjs";
|
|
8
|
-
import { mergeStartupFailureChecks } from "./startup-failures.mjs";
|
|
9
|
-
const REPO = { owner: "owner", name: "repo" };
|
|
10
|
-
// ---------------------------------------------------------------------------
|
|
11
|
-
// Helpers
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
function makeCheck(overrides = {}) {
|
|
14
|
-
return {
|
|
15
|
-
name: "tests",
|
|
16
|
-
status: "COMPLETED",
|
|
17
|
-
conclusion: "FAILURE",
|
|
18
|
-
detailsUrl: "https://github.com/owner/repo/actions/runs/99/jobs/1",
|
|
19
|
-
event: "pull_request",
|
|
20
|
-
runId: "run-99",
|
|
21
|
-
category: "failing",
|
|
22
|
-
...overrides,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
function makeJobsResponse(jobs) {
|
|
26
|
-
return {
|
|
27
|
-
ok: true,
|
|
28
|
-
status: 200,
|
|
29
|
-
headers: new Headers({ "content-type": "application/json" }),
|
|
30
|
-
json: () => Promise.resolve({ jobs }),
|
|
31
|
-
text: () => Promise.resolve(JSON.stringify({ jobs })),
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
function makeErrorResponse(status) {
|
|
35
|
-
return {
|
|
36
|
-
ok: false,
|
|
37
|
-
status,
|
|
38
|
-
headers: new Headers(),
|
|
39
|
-
text: () => Promise.resolve("error"),
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
function makeWorkflowRunsResponse(runs) {
|
|
43
|
-
return {
|
|
44
|
-
ok: true,
|
|
45
|
-
status: 200,
|
|
46
|
-
headers: new Headers({ "content-type": "application/json" }),
|
|
47
|
-
json: () => Promise.resolve({ workflow_runs: runs }),
|
|
48
|
-
text: () => Promise.resolve(JSON.stringify({ workflow_runs: runs })),
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
|
-
// Tests
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
|
-
export function registerHooks() {
|
|
55
|
-
beforeEach(() => {
|
|
56
|
-
mockFetch.mockReset();
|
|
57
|
-
process.env["GH_TOKEN"] = "test-token";
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
export { REPO, fetchStartupFailureChecks, makeCheck, makeErrorResponse, makeJobsResponse, makeWorkflowRunsResponse, mergeStartupFailureChecks, mockFetch, triageFailingChecks, };
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
vi.mock("./commands/clean.mts", () => ({
|
|
3
|
-
runClean: vi.fn(),
|
|
4
|
-
}));
|
|
5
|
-
vi.mock("./commands/resolve.mts", () => ({
|
|
6
|
-
runResolveFetch: vi.fn(),
|
|
7
|
-
runResolveMutate: vi.fn(),
|
|
8
|
-
}));
|
|
9
|
-
vi.mock("./commands/log-file.mts", () => ({
|
|
10
|
-
runLogFile: vi.fn(),
|
|
11
|
-
}));
|
|
12
|
-
vi.mock("./commands/commit-suggestion.mts", () => ({
|
|
13
|
-
runCommitSuggestion: vi.fn(),
|
|
14
|
-
}));
|
|
15
|
-
vi.mock("./commands/iterate/index.mts", async (importOriginal) => {
|
|
16
|
-
const actual = await importOriginal();
|
|
17
|
-
return { ...actual, runIterate: vi.fn() };
|
|
18
|
-
});
|
|
19
|
-
import { main } from "./cli-parser.mjs";
|
|
20
|
-
import { runClean } from "./commands/clean.mjs";
|
|
21
|
-
export const mockRunClean = vi.mocked(runClean);
|
|
22
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
23
|
-
let stdoutSpy;
|
|
24
|
-
let stderrSpy;
|
|
25
|
-
export function getStdout() {
|
|
26
|
-
return stdoutSpy.mock.calls.map((c) => c[0]).join("");
|
|
27
|
-
}
|
|
28
|
-
export function getStderr() {
|
|
29
|
-
return stderrSpy.mock.calls.map((c) => c[0]).join("");
|
|
30
|
-
}
|
|
31
|
-
export function registerHooks() {
|
|
32
|
-
beforeEach(() => {
|
|
33
|
-
vi.clearAllMocks();
|
|
34
|
-
process.exitCode = undefined;
|
|
35
|
-
stdoutSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
|
|
36
|
-
stderrSpy = vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
|
37
|
-
});
|
|
38
|
-
afterEach(() => {
|
|
39
|
-
process.exitCode = undefined;
|
|
40
|
-
stdoutSpy.mockRestore();
|
|
41
|
-
stderrSpy.mockRestore();
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
export { main, stderrSpy, stdoutSpy };
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
vi.mock("./commands/check.mts", () => ({ runCheck: vi.fn() }));
|
|
3
|
-
vi.mock("./commands/resolve.mts", () => ({
|
|
4
|
-
runResolveFetch: vi.fn(),
|
|
5
|
-
runResolveMutate: vi.fn(),
|
|
6
|
-
}));
|
|
7
|
-
vi.mock("./commands/commit-suggestion.mts", () => ({
|
|
8
|
-
runCommitSuggestion: vi.fn(),
|
|
9
|
-
}));
|
|
10
|
-
vi.mock("./commands/iterate/index.mts", async (importOriginal) => {
|
|
11
|
-
const actual = await importOriginal();
|
|
12
|
-
return { ...actual, runIterate: vi.fn() };
|
|
13
|
-
});
|
|
14
|
-
vi.mock("./github/client.mts", () => ({
|
|
15
|
-
getRepoInfo: vi.fn().mockResolvedValue({ owner: "owner", name: "repo" }),
|
|
16
|
-
}));
|
|
17
|
-
import { main } from "./cli-parser.mjs";
|
|
18
|
-
import { runCommitSuggestion } from "./commands/commit-suggestion.mjs";
|
|
19
|
-
const mockRunCommitSuggestion = vi.mocked(runCommitSuggestion);
|
|
20
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
|
-
let stdoutSpy;
|
|
22
|
-
let stderrSpy;
|
|
23
|
-
function getStdout() {
|
|
24
|
-
return stdoutSpy.mock.calls.map((c) => c[0]).join("");
|
|
25
|
-
}
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
// Fixtures
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
const SUGGESTION_RESULT = {
|
|
30
|
-
pr: 42,
|
|
31
|
-
repo: "owner/repo",
|
|
32
|
-
threadId: "t1",
|
|
33
|
-
path: "a.ts",
|
|
34
|
-
startLine: 5,
|
|
35
|
-
endLine: 5,
|
|
36
|
-
author: "alice",
|
|
37
|
-
patch: "--- a/a.ts\n+++ b/a.ts\n@@ -5,1 +5,1 @@\n-old\n+new\n",
|
|
38
|
-
commitMessage: "apply fix",
|
|
39
|
-
commitBody: "Co-authored-by: alice <alice@users.noreply.github.com>",
|
|
40
|
-
filesToStage: ["a.ts"],
|
|
41
|
-
postActionInstructions: [
|
|
42
|
-
"Apply the patch to `a.ts`: run `git apply` with the diff shown above.",
|
|
43
|
-
"Stage the file: `git add -- a.ts`",
|
|
44
|
-
'Commit: `git commit -m "apply fix" -m "Co-authored-by: alice <alice@users.noreply.github.com>"`',
|
|
45
|
-
"Resolve the thread on GitHub: `pr-shepherd resolve 42 --resolve-thread-ids t1`",
|
|
46
|
-
"Push when ready: `git push` (or `git push --force-with-lease` after rebasing).",
|
|
47
|
-
],
|
|
48
|
-
};
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
|
-
// commit-suggestion dispatch
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
|
-
export function registerHooks() {
|
|
53
|
-
beforeEach(() => {
|
|
54
|
-
vi.clearAllMocks();
|
|
55
|
-
process.exitCode = undefined;
|
|
56
|
-
stdoutSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
|
|
57
|
-
stderrSpy = vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
|
58
|
-
});
|
|
59
|
-
afterEach(() => {
|
|
60
|
-
process.exitCode = undefined;
|
|
61
|
-
stdoutSpy.mockRestore();
|
|
62
|
-
stderrSpy.mockRestore();
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
export { SUGGESTION_RESULT, getStdout, main, mockRunCommitSuggestion, runCommitSuggestion, stderrSpy, stdoutSpy, };
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
vi.mock("./commands/check.mts", () => ({ runCheck: vi.fn() }));
|
|
3
|
-
vi.mock("./commands/resolve.mts", () => ({
|
|
4
|
-
runResolveFetch: vi.fn(),
|
|
5
|
-
runResolveMutate: vi.fn(),
|
|
6
|
-
}));
|
|
7
|
-
vi.mock("./commands/commit-suggestion.mts", () => ({
|
|
8
|
-
runCommitSuggestion: vi.fn(),
|
|
9
|
-
}));
|
|
10
|
-
vi.mock("./commands/iterate/index.mts", async (importOriginal) => {
|
|
11
|
-
const actual = await importOriginal();
|
|
12
|
-
return { ...actual, runIterate: vi.fn() };
|
|
13
|
-
});
|
|
14
|
-
vi.mock("./github/client.mts", () => ({
|
|
15
|
-
getRepoInfo: vi.fn().mockResolvedValue({ owner: "owner", name: "repo" }),
|
|
16
|
-
}));
|
|
17
|
-
import { main } from "./cli-parser.mjs";
|
|
18
|
-
import { runIterate } from "./commands/iterate/index.mjs";
|
|
19
|
-
import { makeIterateResult } from "./cli-parser.iterate-fixtures.mjs";
|
|
20
|
-
const mockRunIterate = vi.mocked(runIterate);
|
|
21
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
-
let stdoutSpy;
|
|
23
|
-
let stderrSpy;
|
|
24
|
-
function getStdout() {
|
|
25
|
-
return stdoutSpy.mock.calls.map((c) => c[0]).join("");
|
|
26
|
-
}
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
// formatIterateResult — fix_code actions and ## Checks section
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
export function registerHooks() {
|
|
31
|
-
beforeEach(() => {
|
|
32
|
-
vi.clearAllMocks();
|
|
33
|
-
process.exitCode = undefined;
|
|
34
|
-
stdoutSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
|
|
35
|
-
stderrSpy = vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
|
36
|
-
});
|
|
37
|
-
afterEach(() => {
|
|
38
|
-
process.exitCode = undefined;
|
|
39
|
-
stdoutSpy.mockRestore();
|
|
40
|
-
stderrSpy.mockRestore();
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
export { getStdout, main, makeIterateResult, mockRunIterate, runIterate, stderrSpy, stdoutSpy };
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
export function makeIterateResult(action = "wait") {
|
|
2
|
-
const base = {
|
|
3
|
-
pr: 42,
|
|
4
|
-
repo: "owner/repo",
|
|
5
|
-
status: "IN_PROGRESS",
|
|
6
|
-
state: "OPEN",
|
|
7
|
-
mergeStateStatus: "BLOCKED",
|
|
8
|
-
mergeStatus: "BLOCKED",
|
|
9
|
-
reviewDecision: null,
|
|
10
|
-
blockingBotReviewInProgress: false,
|
|
11
|
-
isDraft: false,
|
|
12
|
-
shouldCancel: false,
|
|
13
|
-
remainingSeconds: 60,
|
|
14
|
-
summary: { passing: 0, skipped: 0, filtered: 0, inProgress: 1 },
|
|
15
|
-
baseBranch: "main",
|
|
16
|
-
branchProtection: null,
|
|
17
|
-
checks: [],
|
|
18
|
-
};
|
|
19
|
-
if (action === "wait")
|
|
20
|
-
return { ...base, action: "wait", log: "WAIT: 0 passing, 1 in-progress" };
|
|
21
|
-
if (action === "mark_ready")
|
|
22
|
-
return { ...base, action: "mark_ready", markedReady: true, log: "MARKED READY: PR 42" };
|
|
23
|
-
if (action === "fix_code") {
|
|
24
|
-
return {
|
|
25
|
-
...base,
|
|
26
|
-
action: "fix_code",
|
|
27
|
-
fix: {
|
|
28
|
-
threads: [],
|
|
29
|
-
resolutionOnlyThreads: [],
|
|
30
|
-
actionableComments: [],
|
|
31
|
-
reviewSummaryIds: [],
|
|
32
|
-
firstLookSummaries: [],
|
|
33
|
-
editedSummaries: [],
|
|
34
|
-
surfacedApprovals: [],
|
|
35
|
-
checks: [],
|
|
36
|
-
changesRequestedReviews: [],
|
|
37
|
-
resolveCommand: {
|
|
38
|
-
argv: ["pr-shepherd", "resolve", "42"],
|
|
39
|
-
requiresHeadSha: true,
|
|
40
|
-
requiresDismissMessage: false,
|
|
41
|
-
hasMutations: false,
|
|
42
|
-
},
|
|
43
|
-
instructions: [
|
|
44
|
-
"Stop this iteration — if you pushed new commits, CI needs time before the next tick; otherwise stop before the next tick.",
|
|
45
|
-
],
|
|
46
|
-
firstLookThreads: [],
|
|
47
|
-
firstLookComments: [],
|
|
48
|
-
inProgressRunIds: [],
|
|
49
|
-
},
|
|
50
|
-
cancelled: [],
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
if (action === "cancel")
|
|
54
|
-
return {
|
|
55
|
-
...base,
|
|
56
|
-
action: "cancel",
|
|
57
|
-
reason: "ready-delay-elapsed",
|
|
58
|
-
log: "CANCEL: PR #42 — stopping",
|
|
59
|
-
};
|
|
60
|
-
if (action === "escalate") {
|
|
61
|
-
return {
|
|
62
|
-
...base,
|
|
63
|
-
action: "escalate",
|
|
64
|
-
escalate: {
|
|
65
|
-
triggers: [],
|
|
66
|
-
unresolvedThreads: [],
|
|
67
|
-
ambiguousComments: [],
|
|
68
|
-
changesRequestedReviews: [],
|
|
69
|
-
suggestion: "check manually",
|
|
70
|
-
humanMessage: "⚠️ /pr-shepherd:pr-shepherd paused — manual intervention required",
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
return { ...base, action: "wait", log: "WAIT: 0 passing, 1 in-progress" };
|
|
75
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
vi.mock("./commands/check.mts", () => ({ runCheck: vi.fn() }));
|
|
3
|
-
vi.mock("./commands/resolve.mts", () => ({
|
|
4
|
-
runResolveFetch: vi.fn(),
|
|
5
|
-
runResolveMutate: vi.fn(),
|
|
6
|
-
}));
|
|
7
|
-
vi.mock("./commands/commit-suggestion.mts", () => ({
|
|
8
|
-
runCommitSuggestion: vi.fn(),
|
|
9
|
-
}));
|
|
10
|
-
vi.mock("./commands/iterate/index.mts", async (importOriginal) => {
|
|
11
|
-
const actual = await importOriginal();
|
|
12
|
-
return { ...actual, runIterate: vi.fn() };
|
|
13
|
-
});
|
|
14
|
-
import { main } from "./cli-parser.mjs";
|
|
15
|
-
import { runIterate } from "./commands/iterate/index.mjs";
|
|
16
|
-
import { formatIterateResult } from "./cli/iterate-formatter.mjs";
|
|
17
|
-
import { makeIterateResult } from "./cli-parser.iterate-fixtures.mjs";
|
|
18
|
-
const mockRunIterate = vi.mocked(runIterate);
|
|
19
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
-
let stdoutSpy;
|
|
21
|
-
let stderrSpy;
|
|
22
|
-
function getStdout() {
|
|
23
|
-
return stdoutSpy.mock.calls.map((c) => c[0]).join("");
|
|
24
|
-
}
|
|
25
|
-
function getStderr() {
|
|
26
|
-
return stderrSpy.mock.calls.map((c) => c[0]).join("");
|
|
27
|
-
}
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
// iterate dispatch
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
export function registerHooks() {
|
|
32
|
-
beforeEach(() => {
|
|
33
|
-
vi.clearAllMocks();
|
|
34
|
-
process.exitCode = undefined;
|
|
35
|
-
stdoutSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
|
|
36
|
-
stderrSpy = vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
|
37
|
-
});
|
|
38
|
-
afterEach(() => {
|
|
39
|
-
process.exitCode = undefined;
|
|
40
|
-
stdoutSpy.mockRestore();
|
|
41
|
-
stderrSpy.mockRestore();
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
export { formatIterateResult, getStderr, getStdout, main, makeIterateResult, mockRunIterate, runIterate, stderrSpy, stdoutSpy, };
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { vi, beforeEach, afterEach } from "vitest";
|
|
3
|
-
vi.mock("./commands/resolve.mts", () => ({
|
|
4
|
-
runResolveFetch: vi.fn(),
|
|
5
|
-
runResolveMutate: vi.fn(),
|
|
6
|
-
}));
|
|
7
|
-
vi.mock("./commands/log-file.mts", () => ({
|
|
8
|
-
runLogFile: vi.fn(),
|
|
9
|
-
}));
|
|
10
|
-
vi.mock("./commands/commit-suggestion.mts", () => ({
|
|
11
|
-
runCommitSuggestion: vi.fn(),
|
|
12
|
-
}));
|
|
13
|
-
vi.mock("./commands/mark-files-as-viewed.mts", () => ({
|
|
14
|
-
runMarkFilesAsViewed: vi.fn(),
|
|
15
|
-
}));
|
|
16
|
-
vi.mock("./commands/iterate/index.mts", async (importOriginal) => {
|
|
17
|
-
const actual = await importOriginal();
|
|
18
|
-
return { ...actual, runIterate: vi.fn() };
|
|
19
|
-
});
|
|
20
|
-
import { main } from "./cli-parser.mjs";
|
|
21
|
-
import { runLogFile } from "./commands/log-file.mjs";
|
|
22
|
-
import { runResolveFetch, runResolveMutate } from "./commands/resolve.mjs";
|
|
23
|
-
import { runMarkFilesAsViewed } from "./commands/mark-files-as-viewed.mjs";
|
|
24
|
-
const mockRunResolveFetch = vi.mocked(runResolveFetch);
|
|
25
|
-
const mockRunResolveMutate = vi.mocked(runResolveMutate);
|
|
26
|
-
const mockRunLogFile = vi.mocked(runLogFile);
|
|
27
|
-
const mockRunMarkFilesAsViewed = vi.mocked(runMarkFilesAsViewed);
|
|
28
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
|
-
let stdoutSpy;
|
|
30
|
-
let stderrSpy;
|
|
31
|
-
function getStdout() {
|
|
32
|
-
return stdoutSpy.mock.calls.map((c) => c[0]).join("");
|
|
33
|
-
}
|
|
34
|
-
export function registerHooks() {
|
|
35
|
-
beforeEach(() => {
|
|
36
|
-
vi.clearAllMocks();
|
|
37
|
-
process.exitCode = undefined;
|
|
38
|
-
stdoutSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
|
|
39
|
-
stderrSpy = vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
|
40
|
-
});
|
|
41
|
-
afterEach(() => {
|
|
42
|
-
process.exitCode = undefined;
|
|
43
|
-
stdoutSpy.mockRestore();
|
|
44
|
-
stderrSpy.mockRestore();
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
export { getStdout, main, mockRunLogFile, mockRunMarkFilesAsViewed, mockRunResolveFetch, mockRunResolveMutate, readFileSync, runLogFile, runResolveFetch, runResolveMutate, stderrSpy, stdoutSpy, };
|
|
@@ -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
|
-
}
|