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,127 +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", () => ({
16
- getCurrentPrNumber: vi.fn().mockResolvedValue(42),
17
- }));
18
- vi.mock("../state/fix-attempts.mts", () => ({
19
- readFixAttempts: vi.fn().mockResolvedValue(null),
20
- writeFixAttempts: vi.fn().mockResolvedValue(undefined),
21
- }));
22
- vi.mock("../state/iterate-stall.mts", () => ({
23
- readStallState: vi.fn().mockResolvedValue(null),
24
- writeStallState: vi.fn().mockResolvedValue(undefined),
25
- }));
26
- vi.mock("../state/seen-comments.mts", async (importOriginal) => {
27
- const actual = await importOriginal();
28
- return {
29
- ...actual,
30
- markSeen: vi.fn().mockResolvedValue(undefined),
31
- };
32
- });
33
- const { mockLoadConfig } = vi.hoisted(() => ({ mockLoadConfig: vi.fn() }));
34
- vi.mock("../config/load.mts", () => ({ loadConfig: mockLoadConfig }));
35
- import { runIterate } from "./iterate/index.mjs";
36
- import { runCheck } from "./check.mjs";
37
- import { updateReadyDelay } from "./ready-delay.mjs";
38
- import { readFixAttempts, writeFixAttempts } from "../state/fix-attempts.mjs";
39
- import { readStallState, writeStallState } from "../state/iterate-stall.mjs";
40
- import { markSeen } from "../state/seen-comments.mjs";
41
- const mockRunCheck = vi.mocked(runCheck);
42
- const mockUpdateReadyDelay = vi.mocked(updateReadyDelay);
43
- const mockReadFixAttempts = vi.mocked(readFixAttempts);
44
- const mockWriteFixAttempts = vi.mocked(writeFixAttempts);
45
- const mockReadStallState = vi.mocked(readStallState);
46
- const mockWriteStallState = vi.mocked(writeStallState);
47
- const mockMarkSeen = vi.mocked(markSeen);
48
- function makeReport(overrides = {}) {
49
- return {
50
- pr: 42,
51
- nodeId: "PR_kgDOAAA",
52
- repo: "owner/repo",
53
- status: "READY",
54
- baseBranch: "main",
55
- branchProtection: null,
56
- mergeStatus: {
57
- status: "CLEAN",
58
- state: "OPEN",
59
- isDraft: false,
60
- mergeable: "MERGEABLE",
61
- reviewDecision: "APPROVED",
62
- blockingBotReviewInProgress: false,
63
- mergeStateStatus: "CLEAN",
64
- },
65
- checks: {
66
- passing: [],
67
- failing: [],
68
- inProgress: [],
69
- skipped: [],
70
- filtered: [],
71
- filteredNames: [],
72
- blockedByFilteredCheck: false,
73
- },
74
- threads: {
75
- actionable: [],
76
- resolutionOnly: [],
77
- autoResolved: [],
78
- autoResolveErrors: [],
79
- firstLook: [],
80
- },
81
- comments: { actionable: [], firstLook: [] },
82
- changesRequestedReviews: [],
83
- reviewSummaries: [],
84
- firstLookSummaries: [],
85
- editedSummaries: [],
86
- approvedReviews: [],
87
- ...overrides,
88
- };
89
- }
90
- function makeOpts(overrides = {}) {
91
- return {
92
- prNumber: 42,
93
- format: "json",
94
- readyDelaySeconds: 600,
95
- ...overrides,
96
- };
97
- }
98
- export function registerHooks() {
99
- beforeEach(() => {
100
- vi.clearAllMocks();
101
- mockExecFile.mockResolvedValue({ stdout: "abc1234\n", stderr: "" });
102
- mockLoadConfig.mockReturnValue({
103
- iterate: {
104
- fixAttemptsPerThread: 3,
105
- stallTimeoutMinutes: 60,
106
- minimizeApprovals: false,
107
- },
108
- watch: { readyDelayMinutes: 10 },
109
- resolve: {
110
- concurrency: 4,
111
- shaPoll: { intervalMs: 2000, maxAttempts: 10 },
112
- fetchReviewSummaries: true,
113
- },
114
- checks: { ciTriggerEvents: ["pull_request"] },
115
- mergeStatus: { blockingReviewerLogins: [] },
116
- actions: { autoResolveOutdated: false, autoMarkReady: false, commitSuggestions: false },
117
- });
118
- mockReadFixAttempts.mockResolvedValue(null);
119
- mockWriteFixAttempts.mockResolvedValue(undefined);
120
- mockReadStallState.mockResolvedValue(null);
121
- mockWriteStallState.mockResolvedValue(undefined);
122
- });
123
- afterEach(() => {
124
- vi.restoreAllMocks();
125
- });
126
- }
127
- export { makeOpts, makeReport, mockExecFile, mockFetch, mockLoadConfig, mockReadFixAttempts, mockReadStallState, mockMarkSeen, mockRunCheck, mockUpdateReadyDelay, mockWriteFixAttempts, mockWriteStallState, readFixAttempts, readStallState, runCheck, runIterate, updateReadyDelay, writeFixAttempts, writeStallState, };
@@ -1,77 +0,0 @@
1
- import { vi, beforeEach, afterEach } from "vitest";
2
- vi.mock("./iterate/index.mts", () => ({ runIterate: vi.fn() }));
3
- import { runIterate } from "./iterate/index.mjs";
4
- const mockRunIterate = vi.mocked(runIterate);
5
- function makeWaitResult(overrides = {}) {
6
- return {
7
- action: "wait",
8
- pr: 42,
9
- repo: "owner/repo",
10
- status: "IN_PROGRESS",
11
- state: "OPEN",
12
- mergeStateStatus: "BLOCKED",
13
- mergeStatus: "BLOCKED",
14
- reviewDecision: "REVIEW_REQUIRED",
15
- blockingBotReviewInProgress: false,
16
- isDraft: false,
17
- shouldCancel: false,
18
- remainingSeconds: 0,
19
- summary: { passing: 2, failing: 0, inProgress: 1, skipped: 0, filtered: 0 },
20
- baseBranch: "main",
21
- checks: [],
22
- log: "WAIT: 2 passing, 1 in-progress",
23
- ...overrides,
24
- };
25
- }
26
- function makeCancelResult() {
27
- return {
28
- action: "cancel",
29
- pr: 42,
30
- repo: "owner/repo",
31
- status: "READY",
32
- state: "MERGED",
33
- mergeStateStatus: "CLEAN",
34
- mergeStatus: "CLEAN",
35
- reviewDecision: "APPROVED",
36
- blockingBotReviewInProgress: false,
37
- isDraft: false,
38
- shouldCancel: true,
39
- remainingSeconds: 0,
40
- summary: { passing: 3, failing: 0, inProgress: 0, skipped: 0, filtered: 0 },
41
- baseBranch: "main",
42
- checks: [],
43
- reason: "merged",
44
- log: "CANCEL: PR #42 is merged — stopping",
45
- };
46
- }
47
- function makeMarkReadyResult() {
48
- return {
49
- action: "mark_ready",
50
- pr: 42,
51
- repo: "owner/repo",
52
- status: "READY",
53
- state: "OPEN",
54
- mergeStateStatus: "CLEAN",
55
- mergeStatus: "CLEAN",
56
- reviewDecision: "APPROVED",
57
- blockingBotReviewInProgress: false,
58
- isDraft: false,
59
- shouldCancel: false,
60
- remainingSeconds: 0,
61
- summary: { passing: 3, failing: 0, inProgress: 0, skipped: 0, filtered: 0 },
62
- baseBranch: "main",
63
- checks: [],
64
- markedReady: true,
65
- log: "MARKED READY: PR #42 converted from draft to ready for review",
66
- };
67
- }
68
- function registerPollHooks() {
69
- beforeEach(() => {
70
- vi.clearAllMocks();
71
- vi.useFakeTimers();
72
- });
73
- afterEach(() => {
74
- vi.useRealTimers();
75
- });
76
- }
77
- export { mockRunIterate, makeWaitResult, makeCancelResult, makeMarkReadyResult, registerPollHooks };
@@ -1,59 +0,0 @@
1
- import { buildPrShepherdCommand } from "../cli/runner.mjs";
2
- import { SHEPHERD_JOURNAL_REFERENCE_GUIDANCE_THREADS_AND_COMMENTS_IN_ITEMS, buildShepherdJournalInstruction, } from "./shepherd-journal.mjs";
3
- import { buildCommitSuggestionInstruction } from "./commit-suggestion-instruction.mjs";
4
- /**
5
- * Build the numbered triage/fix/resolve instruction steps for the agent to follow.
6
- * Steps are conditionally emitted based on what the fetch returned (mirrors
7
- * `buildFixInstructions` in `commands/iterate/render.mts`).
8
- */
9
- export function buildFetchInstructions(prNumber, result) {
10
- const { actionableThreads, resolutionOnlyThreads, firstLookThreads, actionableComments, firstLookComments, changesRequestedReviews, reviewSummaries, commitSuggestionsEnabled, } = result;
11
- const firstLookTotal = firstLookThreads.length + firstLookComments.length;
12
- const total = actionableThreads.length +
13
- resolutionOnlyThreads.length +
14
- actionableComments.length +
15
- changesRequestedReviews.length +
16
- reviewSummaries.length +
17
- firstLookTotal;
18
- if (total === 0) {
19
- return ["No actionable items and no first-look items — end this invocation."];
20
- }
21
- const hasCodeItems = actionableThreads.length > 0 ||
22
- actionableComments.length > 0 ||
23
- changesRequestedReviews.length > 0;
24
- const hasSuggestions = commitSuggestionsEnabled && actionableThreads.some((t) => t.suggestion != null);
25
- const instructions = [];
26
- instructions.push(`Classify every item listed above into exactly one of: Fixed / Actionable / Not relevant / Outdated / Acknowledge. Do not silently skip any item. Bot-authored review summaries (authors whose name contains \`[bot]\` or matches \`copilot-pull-request-reviewer\`, \`gemini-code-assist\`) default to Acknowledge with reason "bot summary — no actionable content" unless the body calls out an unaddressed issue.`);
27
- if (firstLookTotal > 0) {
28
- instructions.push(`Items in \`## First-look items\` are shown so you can acknowledge their current status before acting. If a first-look thread also appears under \`## Review threads to resolve\`, include its ID in \`--resolve-thread-ids\`; otherwise do not pass first-look-only IDs to mutation flags.`);
29
- }
30
- const editedTotal = actionableComments.filter((c) => c.edited).length +
31
- firstLookThreads.filter((t) => t.edited).length +
32
- firstLookComments.filter((c) => c.edited).length;
33
- if (editedTotal > 0) {
34
- instructions.push(`Actionable comments marked \`[edited since first look]\` and first-look bullets tagged \`, edited\` were updated by their author after you previously acknowledged them. Read the updated body before deciding whether any matching \`## Review threads to resolve\` item should be resolved.`);
35
- }
36
- if (hasSuggestions) {
37
- instructions.push(buildCommitSuggestionInstruction(prNumber, "## Actionable Review Threads", true));
38
- }
39
- if (hasCodeItems) {
40
- instructions.push(`Read and edit each file referenced under \`## Actionable Review Threads\`, \`## Actionable PR Comments\`, and \`## Pending CHANGES_REQUESTED reviews\` above. Reclassify each fixed item as Fixed. If an item is too complex to address, leave it as Actionable for the final report.`);
41
- instructions.push(`If you applied code edits: commit them with a descriptive message, cancel any stale in-progress runs, then rebase and push per your repository's conventions.`);
42
- }
43
- if (resolutionOnlyThreads.length > 0) {
44
- instructions.push(`Resolve each thread under \`## Review threads to resolve\` with \`--resolve-thread-ids\`. These threads are already outdated or minimized, so no code edit is required for them unless their body reveals separate work you choose to do.`);
45
- }
46
- const requireShaHint = hasCodeItems
47
- ? ` Include \`--require-sha $(git rev-parse HEAD)\` only when you pushed new commits.`
48
- : "";
49
- const dismissNote = changesRequestedReviews.length > 0
50
- ? ` For \`--dismiss-review-ids\`: \`--message\` is required with one specific sentence describing the fix or the reason for not acting (no boilerplate like "address review comments"); omit \`--message\` when not dismissing. Review-summary IDs (\`PRR_…\` from \`## Review summaries\`) go into \`--minimize-comment-ids\`, never \`--dismiss-review-ids\`.`
51
- : reviewSummaries.length > 0
52
- ? ` Review-summary IDs (\`PRR_…\` from \`## Review summaries\`) go into \`--minimize-comment-ids\`.`
53
- : "";
54
- const resolveCommand = `${buildPrShepherdCommand(["resolve", String(prNumber)]).text} [--resolve-thread-ids <ids>] [--minimize-comment-ids <ids>] [--dismiss-review-ids <ids> --message "<reason>"]`;
55
- instructions.push(`Run \`${resolveCommand}\` with only the non-empty flag subsets. Skip the command entirely if all three ID lists are empty.${requireShaHint}${dismissNote}`);
56
- instructions.push(buildShepherdJournalInstruction(prNumber, SHEPHERD_JOURNAL_REFERENCE_GUIDANCE_THREADS_AND_COMMENTS_IN_ITEMS));
57
- instructions.push(`Report: echo the CLI's mutation output, then one line per Acknowledged item: \`Acknowledged <id> (@<author>): <reason>\`. If any fetched item was neither resolved nor acknowledged, stop and escalate: "<N> item(s) fetched but not acted on or acknowledged — need human direction before closing".`);
58
- return instructions;
59
- }
@@ -1,116 +0,0 @@
1
- import { vi, beforeEach } from "vitest";
2
- vi.mock("../github/client.mts", () => ({
3
- getRepoInfo: vi.fn().mockResolvedValue({ owner: "owner", name: "repo" }),
4
- getCurrentPrNumber: vi.fn().mockResolvedValue(42),
5
- }));
6
- vi.mock("../state/seen-comments.mts", async (importOriginal) => {
7
- const actual = await importOriginal();
8
- return {
9
- ...actual,
10
- loadSeenMap: vi.fn().mockResolvedValue(new Map()),
11
- markSeen: vi.fn().mockResolvedValue(undefined),
12
- };
13
- });
14
- vi.mock("../github/batch.mts", () => ({
15
- fetchPrBatch: vi.fn(),
16
- }));
17
- vi.mock("../comments/resolve.mts", () => ({
18
- autoResolveOutdated: vi.fn(),
19
- applyResolveOptions: vi.fn(),
20
- }));
21
- vi.mock("../config/load.mts", () => ({
22
- loadConfig: vi.fn().mockReturnValue({
23
- resolve: {
24
- shaPoll: { intervalMs: 2000, maxAttempts: 10 },
25
- fetchReviewSummaries: true,
26
- },
27
- actions: {
28
- autoResolveOutdated: true,
29
- autoMarkReady: true,
30
- commitSuggestions: true,
31
- },
32
- }),
33
- }));
34
- import { runResolveFetch, runResolveMutate } from "./resolve.mjs";
35
- import { getCurrentPrNumber } from "../github/client.mjs";
36
- import { fetchPrBatch } from "../github/batch.mjs";
37
- import { autoResolveOutdated, applyResolveOptions } from "../comments/resolve.mjs";
38
- import { loadConfig } from "../config/load.mjs";
39
- import { loadSeenMap, markSeen, hashBody } from "../state/seen-comments.mjs";
40
- const mockGetCurrentPrNumber = vi.mocked(getCurrentPrNumber);
41
- const mockFetchPrBatch = vi.mocked(fetchPrBatch);
42
- const mockAutoResolveOutdated = vi.mocked(autoResolveOutdated);
43
- const mockApplyResolveOptions = vi.mocked(applyResolveOptions);
44
- const mockLoadConfig = vi.mocked(loadConfig);
45
- const mockLoadSeenMap = vi.mocked(loadSeenMap);
46
- const mockMarkSeen = vi.mocked(markSeen);
47
- const BASE_OPTS = { format: "text" };
48
- function makeBatchData(overrides = {}) {
49
- return {
50
- nodeId: "PR_kgDOAAA",
51
- number: 42,
52
- state: "OPEN",
53
- isDraft: false,
54
- mergeable: "MERGEABLE",
55
- mergeStateStatus: "CLEAN",
56
- reviewDecision: "APPROVED",
57
- headRefOid: "abc123",
58
- headRefName: "feature",
59
- headRepoWithOwner: "owner/repo",
60
- baseRefName: "main",
61
- reviewRequests: [],
62
- latestReviews: [],
63
- reviewThreads: [],
64
- comments: [],
65
- changesRequestedReviews: [],
66
- reviewSummaries: [],
67
- approvedReviews: [],
68
- checks: [],
69
- branchProtection: null,
70
- ...overrides,
71
- };
72
- }
73
- function makeThread(overrides = {}) {
74
- return {
75
- id: "t-1",
76
- isResolved: false,
77
- isOutdated: false,
78
- isMinimized: false,
79
- path: "src/foo.ts",
80
- line: 1,
81
- startLine: null,
82
- author: "alice",
83
- authorType: "Unknown",
84
- body: "fix this",
85
- url: "",
86
- createdAtUnix: 1_700_000_000,
87
- ...overrides,
88
- };
89
- }
90
- function makeComment(overrides = {}) {
91
- return {
92
- id: "c-1",
93
- isMinimized: false,
94
- author: "bob",
95
- authorType: "Unknown",
96
- body: "nit",
97
- url: "",
98
- createdAtUnix: 1_700_000_000,
99
- ...overrides,
100
- };
101
- }
102
- export function registerHooks() {
103
- beforeEach(() => {
104
- vi.clearAllMocks();
105
- mockFetchPrBatch.mockResolvedValue({ data: makeBatchData() });
106
- mockAutoResolveOutdated.mockResolvedValue({ resolved: [], errors: [] });
107
- mockApplyResolveOptions.mockResolvedValue({
108
- repliedThreads: [],
109
- resolvedThreads: [],
110
- minimizedComments: [],
111
- dismissedReviews: [],
112
- errors: [],
113
- });
114
- });
115
- }
116
- export { BASE_OPTS, applyResolveOptions, autoResolveOutdated, fetchPrBatch, getCurrentPrNumber, hashBody, loadConfig, loadSeenMap, makeBatchData, makeComment, makeThread, markSeen, mockApplyResolveOptions, mockAutoResolveOutdated, mockFetchPrBatch, mockGetCurrentPrNumber, mockLoadConfig, mockLoadSeenMap, mockMarkSeen, runResolveFetch, runResolveMutate, };
@@ -1,7 +0,0 @@
1
- import { buildShepherdJournalInstruction, SHEPHERD_JOURNAL_APPEND_HINT, SHEPHERD_JOURNAL_FIRST_LOOK_GUIDANCE, SHEPHERD_JOURNAL_REFERENCE_GUIDANCE_THREADS_AND_COMMENTS_IN_ITEM_HEADINGS, SHEPHERD_JOURNAL_REFERENCE_GUIDANCE_THREADS_AND_COMMENTS_IN_ITEMS, SHEPHERD_JOURNAL_SECTION, SHEPHERD_JOURNAL_SECTION_PATTERN, } from "./shepherd-journal.mjs";
2
- import { buildFixInstructions } from "./iterate/render.mjs";
3
- import { buildFetchInstructions } from "./resolve-instructions.mjs";
4
- function countMentions(text, phrase) {
5
- return (text.match(new RegExp(phrase, "g")) ?? []).length;
6
- }
7
- export { SHEPHERD_JOURNAL_APPEND_HINT, SHEPHERD_JOURNAL_FIRST_LOOK_GUIDANCE, SHEPHERD_JOURNAL_REFERENCE_GUIDANCE_THREADS_AND_COMMENTS_IN_ITEMS, SHEPHERD_JOURNAL_REFERENCE_GUIDANCE_THREADS_AND_COMMENTS_IN_ITEM_HEADINGS, SHEPHERD_JOURNAL_SECTION, SHEPHERD_JOURNAL_SECTION_PATTERN, buildFetchInstructions, buildFixInstructions, buildShepherdJournalInstruction, countMentions, };
@@ -1,15 +0,0 @@
1
- /**
2
- * Determines which review threads should be auto-resolved as outdated.
3
- *
4
- * A thread is eligible for auto-resolution when:
5
- * - `isOutdated == true` (GitHub marks these when the diff hunk changed), AND
6
- * - `isResolved == false`.
7
- *
8
- * GitHub's `isOutdated` flag means the thread's referenced code has changed
9
- * enough that the comment no longer points to a live diff line. These threads
10
- * are visually collapsed on GitHub and are safe to resolve programmatically.
11
- */
12
- /** Returns the subset of threads that should be auto-resolved as outdated. */
13
- export function getOutdatedThreads(threads) {
14
- return threads.filter((t) => t.isOutdated && !t.isResolved);
15
- }
@@ -1,44 +0,0 @@
1
- import { vi, beforeEach } from "vitest";
2
- // ---------------------------------------------------------------------------
3
- // Mock github/client.mts before any imports.
4
- // ---------------------------------------------------------------------------
5
- vi.mock("../github/client.mts", () => ({
6
- graphqlWithRateLimit: vi.fn(),
7
- getPrHeadSha: vi.fn(),
8
- }));
9
- import { applyResolveOptions, autoResolveOutdated } from "./resolve.mjs";
10
- import { graphqlWithRateLimit, getPrHeadSha } from "../github/client.mjs";
11
- const mockGraphql = vi.mocked(graphqlWithRateLimit);
12
- const mockGetPrHeadSha = vi.mocked(getPrHeadSha);
13
- const REPO = { owner: "owner", name: "repo" };
14
- /** Build a mock response with the correct nested shape for each alias type (r/m/d). */
15
- function makeBulkResponse(doc) {
16
- const str = typeof doc === "string" ? doc : "";
17
- const data = {};
18
- for (const match of str.matchAll(/^\s+([a-z]\d+):/gm)) {
19
- const alias = match[1];
20
- if (alias === undefined)
21
- continue;
22
- if (alias.startsWith("r"))
23
- data[alias] = { thread: { isResolved: true } };
24
- else if (alias.startsWith("p"))
25
- data[alias] = { comment: { id: `${alias}-comment` } };
26
- else if (alias.startsWith("m"))
27
- data[alias] = { minimizedComment: { isMinimized: true } };
28
- else if (alias.startsWith("d"))
29
- data[alias] = { pullRequestReview: { state: "DISMISSED" } };
30
- else
31
- data[alias] = {};
32
- }
33
- return { data };
34
- }
35
- // ---------------------------------------------------------------------------
36
- // applyResolveOptions
37
- // ---------------------------------------------------------------------------
38
- export function registerHooks() {
39
- beforeEach(() => {
40
- vi.clearAllMocks();
41
- mockGraphql.mockImplementation(async (doc) => makeBulkResponse(doc));
42
- });
43
- }
44
- export { REPO, applyResolveOptions, autoResolveOutdated, getPrHeadSha, graphqlWithRateLimit, makeBulkResponse, mockGetPrHeadSha, mockGraphql, };
@@ -1,66 +0,0 @@
1
- import { vi, beforeEach } from "vitest";
2
- vi.mock("./client.mts", () => ({
3
- graphql: vi.fn(),
4
- graphqlWithRateLimit: vi.fn(),
5
- }));
6
- import { fetchPrBatch } from "./batch.mjs";
7
- import { graphql, graphqlWithRateLimit } from "./client.mjs";
8
- const mockGraphql = vi.mocked(graphql);
9
- const mockGraphqlWithRateLimit = vi.mocked(graphqlWithRateLimit);
10
- const REPO = { owner: "owner", name: "repo" };
11
- // ---------------------------------------------------------------------------
12
- // Helpers
13
- // ---------------------------------------------------------------------------
14
- function makeRawPr(overrides = {}) {
15
- return {
16
- id: "PR_kgDOAAA",
17
- number: 42,
18
- state: "OPEN",
19
- isDraft: false,
20
- mergeable: "MERGEABLE",
21
- mergeStateStatus: "CLEAN",
22
- reviewDecision: "APPROVED",
23
- headRefOid: "abc123",
24
- headRefName: "feature",
25
- headRepository: { nameWithOwner: "owner/repo" },
26
- baseRefName: "main",
27
- reviewRequests: { nodes: [] },
28
- latestReviews: { nodes: [] },
29
- reviewThreads: {
30
- pageInfo: { hasPreviousPage: false, startCursor: null },
31
- nodes: [],
32
- },
33
- comments: {
34
- pageInfo: { hasPreviousPage: false, startCursor: null },
35
- nodes: [],
36
- },
37
- changesRequestedReviews: {
38
- pageInfo: { hasPreviousPage: false, startCursor: null },
39
- nodes: [],
40
- },
41
- reviewSummaries: {
42
- pageInfo: { hasPreviousPage: false, startCursor: null },
43
- nodes: [],
44
- },
45
- approvedReviews: {
46
- pageInfo: { hasPreviousPage: false, startCursor: null },
47
- nodes: [],
48
- },
49
- commits: { nodes: [{ commit: { statusCheckRollup: null } }] },
50
- ...overrides,
51
- };
52
- }
53
- function makeResponse(pr = makeRawPr()) {
54
- return { data: { repository: { pullRequest: pr } } };
55
- }
56
- // ---------------------------------------------------------------------------
57
- // PR not found
58
- // ---------------------------------------------------------------------------
59
- export function registerHooks() {
60
- beforeEach(() => {
61
- vi.clearAllMocks();
62
- mockGraphql.mockResolvedValue(makeResponse());
63
- mockGraphqlWithRateLimit.mockResolvedValue(makeResponse());
64
- });
65
- }
66
- export { REPO, fetchPrBatch, graphql, graphqlWithRateLimit, makeRawPr, makeResponse, mockGraphql, mockGraphqlWithRateLimit, };
@@ -1,66 +0,0 @@
1
- import { vi, beforeEach } from "vitest";
2
- vi.mock("./client.mts", () => ({
3
- graphql: vi.fn(),
4
- graphqlWithRateLimit: vi.fn(),
5
- }));
6
- import { fetchPrBatch } from "./batch.mjs";
7
- import { graphql, graphqlWithRateLimit } from "./client.mjs";
8
- const mockGraphql = vi.mocked(graphql);
9
- const mockGraphqlWithRateLimit = vi.mocked(graphqlWithRateLimit);
10
- const REPO = { owner: "owner", name: "repo" };
11
- // ---------------------------------------------------------------------------
12
- // Helpers
13
- // ---------------------------------------------------------------------------
14
- function makeRawPr(overrides = {}) {
15
- return {
16
- id: "PR_kgDOAAA",
17
- number: 42,
18
- state: "OPEN",
19
- isDraft: false,
20
- mergeable: "MERGEABLE",
21
- mergeStateStatus: "CLEAN",
22
- reviewDecision: "APPROVED",
23
- headRefOid: "abc123",
24
- headRefName: "feature",
25
- headRepository: { nameWithOwner: "owner/repo" },
26
- baseRefName: "main",
27
- reviewRequests: { nodes: [] },
28
- latestReviews: { nodes: [] },
29
- reviewThreads: {
30
- pageInfo: { hasPreviousPage: false, startCursor: null },
31
- nodes: [],
32
- },
33
- comments: {
34
- pageInfo: { hasPreviousPage: false, startCursor: null },
35
- nodes: [],
36
- },
37
- changesRequestedReviews: {
38
- pageInfo: { hasPreviousPage: false, startCursor: null },
39
- nodes: [],
40
- },
41
- reviewSummaries: {
42
- pageInfo: { hasPreviousPage: false, startCursor: null },
43
- nodes: [],
44
- },
45
- approvedReviews: {
46
- pageInfo: { hasPreviousPage: false, startCursor: null },
47
- nodes: [],
48
- },
49
- commits: { nodes: [{ commit: { statusCheckRollup: null } }] },
50
- ...overrides,
51
- };
52
- }
53
- function makeResponse(pr = makeRawPr()) {
54
- return { data: { repository: { pullRequest: pr } } };
55
- }
56
- // ---------------------------------------------------------------------------
57
- // reviewSummaries — COMMENTED reviews surfaced for agent-driven minimize
58
- // ---------------------------------------------------------------------------
59
- export function registerHooks() {
60
- beforeEach(() => {
61
- vi.clearAllMocks();
62
- mockGraphql.mockResolvedValue(makeResponse());
63
- mockGraphqlWithRateLimit.mockResolvedValue(makeResponse());
64
- });
65
- }
66
- export { REPO, fetchPrBatch, graphql, graphqlWithRateLimit, makeRawPr, makeResponse, mockGraphql, mockGraphqlWithRateLimit, };
@@ -1,55 +0,0 @@
1
- import { vi, beforeEach, afterEach } from "vitest";
2
- import { _resetTokenCache } from "./http.mjs";
3
- export const mockFetch = vi.fn();
4
- vi.stubGlobal("fetch", mockFetch);
5
- const { _mockExecFile } = vi.hoisted(() => ({ _mockExecFile: vi.fn() }));
6
- export const mockExecFile = _mockExecFile;
7
- vi.mock("node:child_process", () => ({
8
- execFile: (cmd, args, optsOrCb, maybeCb) => {
9
- const cb = typeof optsOrCb === "function" ? optsOrCb : maybeCb;
10
- _mockExecFile(cmd, args)
11
- .then((result) => cb(null, result))
12
- .catch((err) => cb(err, { stdout: "", stderr: "" }));
13
- },
14
- }));
15
- export function gqlOk(data) {
16
- return {
17
- ok: true,
18
- status: 200,
19
- headers: new Headers({ "content-type": "application/json" }),
20
- json: () => Promise.resolve({ data }),
21
- text: () => Promise.resolve(JSON.stringify({ data })),
22
- };
23
- }
24
- export function restOk(data) {
25
- return {
26
- ok: true,
27
- status: 200,
28
- headers: new Headers({ "content-type": "application/json" }),
29
- json: () => Promise.resolve(data),
30
- text: () => Promise.resolve(JSON.stringify(data)),
31
- };
32
- }
33
- export function gqlErrors(errors) {
34
- return {
35
- ok: true,
36
- status: 200,
37
- headers: new Headers({ "content-type": "application/json" }),
38
- json: () => Promise.resolve({ data: null, errors }),
39
- text: () => Promise.resolve(JSON.stringify({ data: null, errors })),
40
- };
41
- }
42
- export function registerClientHooks() {
43
- beforeEach(() => {
44
- mockFetch.mockReset();
45
- mockExecFile.mockReset();
46
- _resetTokenCache();
47
- delete process.env["GITHUB_TOKEN"];
48
- delete process.env["GITHUB_PERSONAL_ACCESS_TOKEN"];
49
- process.env["GH_TOKEN"] = "test-token";
50
- });
51
- afterEach(() => {
52
- delete process.env["GH_TOKEN"];
53
- _resetTokenCache();
54
- });
55
- }