pr-shepherd 0.34.0 → 0.35.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 +3 -2
- package/.grok-plugin/marketplace.json +17 -0
- package/README.md +56 -39
- package/bin/api.d.mts +80 -0
- package/bin/api.mjs +236 -0
- package/bin/checks/classify.d.mts +41 -0
- package/bin/checks/startup-failures.d.mts +2 -0
- package/bin/checks/superseded.d.mts +21 -0
- package/bin/checks/triage.d.mts +4 -0
- package/bin/classify/apply.d.mts +18 -0
- package/bin/classify/apply.mjs +4 -0
- package/bin/classify/loader.d.mts +10 -0
- package/bin/classify/types.d.mts +35 -0
- package/bin/cli/args.d.mts +18 -0
- package/bin/cli/clean-formatter.d.mts +2 -0
- package/bin/cli/default-poll.d.mts +2 -0
- package/bin/cli/duration-flag.d.mts +2 -0
- package/bin/cli/duration.d.mts +13 -0
- package/bin/cli/fence.d.mts +1 -0
- package/bin/cli/fix-formatter-extra.d.mts +3 -0
- package/bin/cli/fix-formatter.d.mts +2 -0
- package/bin/cli/fix-formatter.mjs +6 -6
- package/bin/cli/formatters.d.mts +7 -0
- package/bin/cli/handlers.d.mts +4 -0
- package/bin/cli/handlers.mjs +11 -10
- package/bin/cli/help-command-pages.d.mts +231 -0
- package/bin/cli/help-command-pages.mjs +106 -1
- package/bin/cli/help-iterate-poll-pages.d.mts +4 -0
- package/bin/cli/help-iterate-poll-pages.mjs +4 -2
- package/bin/cli/help-log-file-page.d.mts +1 -0
- package/bin/cli/help-top-page.d.mts +1 -0
- package/bin/cli/help-top-page.mjs +14 -15
- package/bin/cli/help.d.mts +236 -0
- package/bin/cli/help.mjs +17 -0
- package/bin/cli/iterate-emitter.d.mts +8 -0
- package/bin/cli/iterate-flags.d.mts +11 -0
- package/bin/cli/iterate-formatter.d.mts +19 -0
- package/bin/cli/iterate-instructions.d.mts +6 -0
- package/bin/cli/iterate-lean.d.mts +12 -0
- package/bin/cli/journal-formatter.d.mts +2 -0
- package/bin/cli/journal-formatter.mjs +15 -0
- package/bin/cli/journal-handler.d.mts +1 -0
- package/bin/cli/journal-handler.mjs +7 -20
- package/bin/cli/list-formatters.d.mts +76 -0
- package/bin/cli/list-formatters.mjs +9 -9
- package/bin/cli/mark-files-as-viewed-flags.d.mts +11 -0
- package/bin/cli/mark-files-as-viewed-formatter.d.mts +2 -0
- package/bin/cli/mutate-formatter.d.mts +2 -0
- package/bin/cli/poll-handler.d.mts +1 -0
- package/bin/cli/poll-handler.mjs +2 -2
- package/bin/cli/resolve-validators.d.mts +3 -0
- package/bin/cli/resolve-validators.mjs +3 -3
- package/bin/cli/runner.d.mts +7 -0
- package/bin/cli/suggestion-renderer.d.mts +3 -0
- package/bin/cli/validate-default-args.d.mts +6 -0
- package/bin/cli-parser.d.mts +2 -0
- package/bin/cli-parser.mjs +72 -16
- package/bin/commands/check-annotations.d.mts +5 -0
- package/bin/commands/check-status.d.mts +3 -0
- package/bin/commands/check-terminal-report.d.mts +5 -0
- package/bin/commands/check.d.mts +6 -0
- package/bin/commands/check.mjs +2 -1
- package/bin/commands/clean.d.mts +21 -0
- package/bin/commands/commit-suggestion-instruction.d.mts +8 -0
- package/bin/commands/commit-suggestion-instruction.mjs +3 -3
- package/bin/commands/commit-suggestion.d.mts +8 -0
- package/bin/commands/commit-suggestion.mjs +11 -10
- package/bin/commands/iterate/check-instructions.d.mts +16 -0
- package/bin/commands/iterate/check-instructions.mjs +3 -3
- package/bin/commands/iterate/classify.d.mts +18 -0
- package/bin/commands/iterate/classify.mjs +4 -4
- package/bin/commands/iterate/escalate.d.mts +31 -0
- package/bin/commands/iterate/escalate.mjs +7 -4
- package/bin/commands/iterate/fix-code.d.mts +25 -0
- package/bin/commands/iterate/helpers.d.mts +13 -0
- package/bin/commands/iterate/helpers.mjs +4 -1
- package/bin/commands/iterate/index.d.mts +2 -0
- package/bin/commands/iterate/index.mjs +5 -2
- package/bin/commands/iterate/render.d.mts +5 -0
- package/bin/commands/iterate/render.mjs +8 -6
- package/bin/commands/iterate/reruns.d.mts +20 -0
- package/bin/commands/iterate/stall.d.mts +6 -0
- package/bin/commands/journal/index.d.mts +14 -0
- package/bin/commands/journal/index.mjs +1 -0
- package/bin/commands/journal/transform.d.mts +22 -0
- package/bin/commands/log-file.d.mts +5 -0
- package/bin/commands/mark-files-as-viewed.d.mts +26 -0
- package/bin/commands/mark-files-as-viewed.mjs +1 -0
- package/bin/commands/poll.d.mts +10 -0
- package/bin/commands/poll.mjs +1 -0
- package/bin/commands/ready-delay.d.mts +29 -0
- package/bin/commands/ready-mergeability.d.mts +15 -0
- package/bin/commands/resolve-mutate.d.mts +4 -0
- package/bin/commands/resolve-mutate.mjs +1 -0
- package/bin/commands/resolve.d.mts +4 -0
- package/bin/commands/shepherd-journal.d.mts +7 -0
- package/bin/commands/shepherd-journal.mjs +2 -2
- package/bin/comments/authors.d.mts +14 -0
- package/bin/comments/marker.d.mts +2 -0
- package/bin/comments/minimize-policy.d.mts +4 -0
- package/bin/comments/pending-ops.d.mts +15 -0
- package/bin/comments/rate-limit.d.mts +18 -0
- package/bin/comments/resolve.d.mts +34 -0
- package/bin/comments/resolve.mjs +1 -0
- package/bin/comments/review-thread-markers.d.mts +8 -0
- package/bin/comments/review-visibility.d.mts +28 -0
- package/bin/comments/sha-poll.d.mts +2 -0
- package/bin/comments/thread-visibility.d.mts +11 -0
- package/bin/comments/visible-comments.d.mts +11 -0
- package/bin/config/load.d.mts +60 -0
- package/bin/config/load.mjs +72 -1
- package/bin/config.json +0 -2
- package/bin/execution-context.d.mts +9 -0
- package/bin/execution-context.mjs +19 -0
- package/bin/exit-codes.d.mts +51 -0
- package/bin/github/activity.d.mts +3 -0
- package/bin/github/activity.mjs +7 -0
- package/bin/github/batch-parser-helpers.d.mts +22 -0
- package/bin/github/batch-parsers.d.mts +3 -0
- package/bin/github/batch-parsers.mjs +6 -0
- package/bin/github/batch-raw-types.d.mts +207 -0
- package/bin/github/batch-response.d.mts +4 -0
- package/bin/github/batch.d.mts +22 -0
- package/bin/github/branch-protection.d.mts +3 -0
- package/bin/github/check-annotations.d.mts +2 -0
- package/bin/github/client.d.mts +46 -0
- package/bin/github/client.mjs +7 -2
- package/bin/github/errors.d.mts +25 -0
- package/bin/github/gql/batch-pr.gql +5 -0
- package/bin/github/gql/review-thread-comments.gql +1 -0
- package/bin/github/graphql-http.d.mts +19 -0
- package/bin/github/graphql-response.d.mts +7 -0
- package/bin/github/http-auth.d.mts +4 -0
- package/bin/github/http-request.d.mts +7 -0
- package/bin/github/http-utils.d.mts +11 -0
- package/bin/github/http.d.mts +5 -0
- package/bin/github/pagination.d.mts +45 -0
- package/bin/github/queries.d.mts +24 -0
- package/bin/github/rest-http.d.mts +2 -0
- package/bin/github/thread-comments.d.mts +2 -0
- package/bin/index.d.mts +10 -0
- package/bin/index.mjs +1 -1
- package/bin/log/log-file.d.mts +28 -0
- package/bin/log/session.d.mts +31 -0
- package/bin/log/setup.d.mts +6 -0
- package/bin/mcp/index.d.mts +5 -0
- package/bin/mcp/index.mjs +8 -0
- package/bin/mcp/server.d.mts +8 -0
- package/bin/mcp/server.mjs +157 -0
- package/bin/mcp-stdio.d.mts +2 -0
- package/bin/mcp-stdio.mjs +7 -0
- package/bin/merge-status/derive.d.mts +19 -0
- package/bin/reporters/agent.d.mts +23 -0
- package/bin/reporters/agent.mjs +3 -0
- package/bin/state/base.d.mts +1 -0
- package/bin/state/bot-cr-seen.d.mts +51 -0
- package/bin/state/bot-cr-seen.mjs +1 -1
- package/bin/state/fix-attempts.d.mts +27 -0
- package/bin/state/iterate-stall.d.mts +27 -0
- package/bin/state/seen-comments.d.mts +62 -0
- package/bin/suggestions/extract.d.mts +8 -0
- package/bin/suggestions/parse.d.mts +48 -0
- package/bin/suggestions/patch.d.mts +14 -0
- package/bin/threads/transcript.d.mts +14 -0
- package/bin/threads/transcript.mjs +4 -0
- package/bin/types/activity.d.mts +30 -0
- package/bin/types/agent-thread.d.mts +9 -0
- package/bin/types/check-annotations.d.mts +14 -0
- package/bin/types/check-classification.d.mts +19 -0
- package/bin/types/github.d.mts +139 -0
- package/bin/types/iterate.d.mts +157 -0
- package/bin/types/protected-run.d.mts +6 -0
- package/bin/types/report.d.mts +176 -0
- package/bin/types/review-thread.d.mts +12 -0
- package/bin/types.d.mts +9 -0
- package/bin/util/markdown.d.mts +1 -0
- package/bin/util/path-segment.d.mts +2 -0
- package/bin/util/sleep.d.mts +1 -0
- package/bin/util/worktree.d.mts +9 -0
- package/bin/util/worktree.mjs +4 -1
- package/package.json +51 -37
- package/plugins/pr-shepherd/.codex-plugin/plugin.json +3 -2
- package/plugins/pr-shepherd/.codex.mcp.json +8 -0
- package/plugins/pr-shepherd/.mcp.json +6 -0
- package/plugins/pr-shepherd/skills/mark-files-as-viewed/SKILL.md +5 -19
- package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +6 -15
- package/src/classify/types.mts +12 -0
|
@@ -82,6 +82,7 @@ query BatchPr(
|
|
|
82
82
|
id
|
|
83
83
|
isMinimized
|
|
84
84
|
url
|
|
85
|
+
authorAssociation
|
|
85
86
|
author {
|
|
86
87
|
__typename
|
|
87
88
|
login
|
|
@@ -107,6 +108,7 @@ query BatchPr(
|
|
|
107
108
|
id
|
|
108
109
|
isMinimized
|
|
109
110
|
url
|
|
111
|
+
authorAssociation
|
|
110
112
|
author {
|
|
111
113
|
__typename
|
|
112
114
|
login
|
|
@@ -126,6 +128,7 @@ query BatchPr(
|
|
|
126
128
|
}
|
|
127
129
|
nodes {
|
|
128
130
|
id
|
|
131
|
+
authorAssociation
|
|
129
132
|
author {
|
|
130
133
|
__typename
|
|
131
134
|
login
|
|
@@ -145,6 +148,7 @@ query BatchPr(
|
|
|
145
148
|
nodes {
|
|
146
149
|
id
|
|
147
150
|
isMinimized
|
|
151
|
+
authorAssociation
|
|
148
152
|
author {
|
|
149
153
|
__typename
|
|
150
154
|
login
|
|
@@ -164,6 +168,7 @@ query BatchPr(
|
|
|
164
168
|
nodes {
|
|
165
169
|
id
|
|
166
170
|
isMinimized
|
|
171
|
+
authorAssociation
|
|
167
172
|
author {
|
|
168
173
|
__typename
|
|
169
174
|
login
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type GitHubGraphQlError } from "./errors.mts";
|
|
2
|
+
import { type RateLimitInfo } from "./http-utils.mts";
|
|
3
|
+
interface GraphQlResult<T = unknown> {
|
|
4
|
+
data: T;
|
|
5
|
+
rateLimit?: RateLimitInfo;
|
|
6
|
+
retryAfterSeconds?: number;
|
|
7
|
+
errors?: GitHubGraphQlError[];
|
|
8
|
+
}
|
|
9
|
+
export interface GraphQlRequestOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Mutation batches use partial GraphQL data to preserve per-alias successes.
|
|
12
|
+
* Read paths must keep the strict default so incomplete snapshots never drive
|
|
13
|
+
* PR state transitions.
|
|
14
|
+
*/
|
|
15
|
+
allowPartialData?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function graphql<T = unknown>(query: string, vars?: Record<string, unknown>, opts?: GraphQlRequestOptions): Promise<GraphQlResult<T>>;
|
|
18
|
+
export declare function graphqlWithRateLimit<T = unknown>(query: string, vars?: Record<string, unknown>, opts?: GraphQlRequestOptions): Promise<GraphQlResult<T>>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type GitHubGraphQlError } from "./errors.mts";
|
|
2
|
+
import type { RateLimitInfo } from "./http-utils.mts";
|
|
3
|
+
export declare function parseGraphQlPayload<T>(parsed: unknown, status: number, rateLimit: RateLimitInfo | null, retryAfterSeconds: number | undefined): {
|
|
4
|
+
data: T | null;
|
|
5
|
+
errors?: GitHubGraphQlError[];
|
|
6
|
+
};
|
|
7
|
+
export declare function formatGraphQlErrors(errors: GitHubGraphQlError[] | undefined): string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function isTransportError(err: unknown): boolean;
|
|
2
|
+
export interface RateLimitInfo {
|
|
3
|
+
remaining: number;
|
|
4
|
+
limit: number;
|
|
5
|
+
resetAt: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function sanitizeBody(body: string): string;
|
|
8
|
+
export declare function redactToken(body: string): string;
|
|
9
|
+
export declare function redactUrl(url: string): string;
|
|
10
|
+
export declare function parseRateLimit(headers: Headers): RateLimitInfo | null;
|
|
11
|
+
export declare function parseRetryAfter(headers: Headers): number | undefined;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { GitHubRequestError } from "./errors.mts";
|
|
2
|
+
export { _resetTokenCache } from "./http-auth.mts";
|
|
3
|
+
export { graphql, graphqlWithRateLimit } from "./graphql-http.mts";
|
|
4
|
+
export { rest, restText } from "./rest-http.mts";
|
|
5
|
+
export type { RateLimitInfo } from "./http-utils.mts";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic GraphQL cursor-based paginator for shepherd.
|
|
3
|
+
*
|
|
4
|
+
* Both paginators accept a `fetchFn` instead of calling `graphql` directly,
|
|
5
|
+
* which makes them testable without any mocking — tests supply a pure function
|
|
6
|
+
* that returns pages of fake data.
|
|
7
|
+
*
|
|
8
|
+
* GitHub's GraphQL connections support two cursor directions:
|
|
9
|
+
* - Forward (`after` + `first`) — used by check contexts.
|
|
10
|
+
* - Backward (`before` + `last`) — used by reviewThreads (default GitHub order).
|
|
11
|
+
*/
|
|
12
|
+
interface PageInfo {
|
|
13
|
+
hasNextPage?: boolean;
|
|
14
|
+
hasPreviousPage?: boolean;
|
|
15
|
+
endCursor?: string | null;
|
|
16
|
+
startCursor?: string | null;
|
|
17
|
+
}
|
|
18
|
+
export interface Connection<T> {
|
|
19
|
+
pageInfo: PageInfo;
|
|
20
|
+
nodes: T[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Paginate forward through a GraphQL connection (`first` / `after` cursors).
|
|
24
|
+
*
|
|
25
|
+
* @param fetchFn Called once per page. Receives the cursor (or null for
|
|
26
|
+
* the very first page) and returns a Connection<T>.
|
|
27
|
+
* @param initialCursor Start from this cursor instead of null. Pass the
|
|
28
|
+
* `endCursor` of an already-fetched page to fetch only
|
|
29
|
+
* the pages *after* it, avoiding a duplicate re-fetch.
|
|
30
|
+
*/
|
|
31
|
+
export declare function paginateForward<T>(fetchFn: (cursor: string | null) => Promise<Connection<T>>, initialCursor?: string | null): Promise<T[]>;
|
|
32
|
+
/**
|
|
33
|
+
* Paginate backward through a GraphQL connection (`last` / `before` cursors).
|
|
34
|
+
*
|
|
35
|
+
* Used for `reviewThreads(last: 100, before: $before)`.
|
|
36
|
+
*
|
|
37
|
+
* @param fetchFn Called once per page. Receives the cursor (or null for
|
|
38
|
+
* the very first page). Returns a Connection<T> with
|
|
39
|
+
* `hasPreviousPage` and `startCursor` in pageInfo.
|
|
40
|
+
* @param initialCursor Start from this cursor instead of null. Pass the
|
|
41
|
+
* `startCursor` of an already-fetched page to avoid
|
|
42
|
+
* re-fetching it (fetch only the pages *before* it).
|
|
43
|
+
*/
|
|
44
|
+
export declare function paginateBackward<T>(fetchFn: (cursor: string | null) => Promise<Connection<T>>, initialCursor?: string | null): Promise<T[]>;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphQL query strings used by pr-shepherd.
|
|
3
|
+
*
|
|
4
|
+
* Static query strings live in src/github/gql/*.gql and are loaded here.
|
|
5
|
+
* Dynamic documents whose content varies per call (e.g. BulkApply in
|
|
6
|
+
* src/comments/resolve.mts) are built at runtime and are exempt — they
|
|
7
|
+
* cannot be expressed as static files.
|
|
8
|
+
*/
|
|
9
|
+
/** The primary batch query that fetches CI + comments + merge status in one round-trip. */
|
|
10
|
+
export declare const BATCH_PR_QUERY: string;
|
|
11
|
+
/** Fetches additional comments for a single review thread when its nested connection paginates. */
|
|
12
|
+
export declare const REVIEW_THREAD_COMMENTS_QUERY: string;
|
|
13
|
+
/** Fetch inline annotations for a single CheckRun by node ID. */
|
|
14
|
+
export declare const CHECK_RUN_ANNOTATIONS_QUERY: string;
|
|
15
|
+
/** Returns the current head commit SHA for a PR. Used by waitForSha polling. */
|
|
16
|
+
export declare const GET_PR_HEAD_SHA_QUERY: string;
|
|
17
|
+
/** Look up PR number by branch name (for getCurrentPrNumber). */
|
|
18
|
+
export declare const PR_NUMBER_BY_BRANCH_QUERY: string;
|
|
19
|
+
/** Convert a draft PR to ready for review. */
|
|
20
|
+
export declare const MARK_PR_READY_MUTATION: string;
|
|
21
|
+
/** Fetch the PR body and node ID. */
|
|
22
|
+
export declare const GET_PR_BODY_QUERY: string;
|
|
23
|
+
/** Update the PR body. */
|
|
24
|
+
export declare const UPDATE_PR_BODY_MUTATION: string;
|
package/bin/index.d.mts
ADDED
package/bin/index.mjs
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Append-only per-worktree markdown log.
|
|
3
|
+
*
|
|
4
|
+
* Log path: $PR_SHEPHERD_STATE_DIR/<owner>-<repo>/worktrees/<basename>-<sha8>.md
|
|
5
|
+
*
|
|
6
|
+
* Always-on by default. Set PR_SHEPHERD_LOG_DISABLED=1 or CI=true to disable.
|
|
7
|
+
* Write failures flip an internal disabled flag so the CLI never crashes because
|
|
8
|
+
* logging failed.
|
|
9
|
+
*/
|
|
10
|
+
/** Returns the next monotonically-increasing entry number for the current session. */
|
|
11
|
+
export declare function nextEntry(): number;
|
|
12
|
+
interface RepoKey {
|
|
13
|
+
owner: string;
|
|
14
|
+
repo: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function getLogFilePath(key: RepoKey): string;
|
|
17
|
+
/**
|
|
18
|
+
* Initialize the log for this process. Must be called before appendEntry().
|
|
19
|
+
* Silently disables logging on any error (no git repo, bad repo name, etc.).
|
|
20
|
+
*/
|
|
21
|
+
export declare function initLog(repoKey: RepoKey): Promise<string | null>;
|
|
22
|
+
/** Append a pre-formatted markdown chunk to the log. No-op if disabled. */
|
|
23
|
+
export declare function appendEntry(markdown: string): void;
|
|
24
|
+
/** Resolve the log path without initializing (for the log-file subcommand). */
|
|
25
|
+
export declare function resolveLogPath(repoKey: RepoKey): Promise<string>;
|
|
26
|
+
/** Exposed for tests to reset module state. */
|
|
27
|
+
export declare function _resetLogState(): void;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Builds the session header markdown block. */
|
|
2
|
+
export declare function buildSessionHeader(argv: string[]): {
|
|
3
|
+
markdown: string;
|
|
4
|
+
};
|
|
5
|
+
interface HttpRequestEntry {
|
|
6
|
+
n: number;
|
|
7
|
+
kind: "GraphQL" | "REST" | "restText";
|
|
8
|
+
method: string;
|
|
9
|
+
url: string;
|
|
10
|
+
body?: unknown;
|
|
11
|
+
}
|
|
12
|
+
interface HttpResponseEntry {
|
|
13
|
+
n: number;
|
|
14
|
+
kind: "GraphQL" | "REST" | "restText";
|
|
15
|
+
method: string;
|
|
16
|
+
url: string;
|
|
17
|
+
status: number;
|
|
18
|
+
durationMs: number;
|
|
19
|
+
/** Parsed response body. Omit for restText. */
|
|
20
|
+
body?: unknown;
|
|
21
|
+
/** Raw text response body. Omit for restText. */
|
|
22
|
+
textBody?: string;
|
|
23
|
+
contentType?: string;
|
|
24
|
+
contentLength?: number;
|
|
25
|
+
/** Set on 401-retry invocations. */
|
|
26
|
+
attempt?: number;
|
|
27
|
+
}
|
|
28
|
+
export declare function formatRequestEntry(entry: HttpRequestEntry): string;
|
|
29
|
+
export declare function formatResponseEntry(entry: HttpResponseEntry): string;
|
|
30
|
+
export declare function formatOutputEntry(text: string, format: "text" | "json"): string;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Initialize the per-worktree log, write the session header, and install a
|
|
3
|
+
* stdout tee that routes all CLI output to the log. No-op after the first call.
|
|
4
|
+
* Silently skips logging when not in a git repo or on any other error.
|
|
5
|
+
*/
|
|
6
|
+
export declare function setupLog(argv: string[]): Promise<void>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type CreatePrShepherdMcpServerOptions } from "./server.mts";
|
|
2
|
+
export { createPrShepherdMcpServer } from "./server.mts";
|
|
3
|
+
export type { CreatePrShepherdMcpServerOptions } from "./server.mts";
|
|
4
|
+
/** Starts the local stdio-only MCP transport. */
|
|
5
|
+
export declare function runPrShepherdMcpStdio(options?: CreatePrShepherdMcpServerOptions): Promise<void>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2
|
+
import { createPrShepherdMcpServer } from "./server.mjs";
|
|
3
|
+
export { createPrShepherdMcpServer } from "./server.mjs";
|
|
4
|
+
/** Starts the local stdio-only MCP transport. */
|
|
5
|
+
export async function runPrShepherdMcpStdio(options = {}) {
|
|
6
|
+
const server = createPrShepherdMcpServer(options);
|
|
7
|
+
await server.connect(new StdioServerTransport());
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { type CreatePrShepherdOptions, type PrShepherd } from "../api.mts";
|
|
3
|
+
export interface CreatePrShepherdMcpServerOptions extends CreatePrShepherdOptions {
|
|
4
|
+
/** Optional injection point for embedding hosts and focused tests. */
|
|
5
|
+
shepherd?: PrShepherd;
|
|
6
|
+
}
|
|
7
|
+
/** Creates a local-only MCP server with the three public Shepherd operations. */
|
|
8
|
+
export declare function createPrShepherdMcpServer(options?: CreatePrShepherdMcpServerOptions): McpServer;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { createPrShepherd, PartialApplyError, PrShepherdValidationError, } from "../api.mjs";
|
|
6
|
+
import { formatJournalResult } from "../cli/journal-formatter.mjs";
|
|
7
|
+
import { formatCommitSuggestionResult, formatIterateResult, formatMarkFilesAsViewedResult, formatMutateResult, } from "../cli/formatters.mjs";
|
|
8
|
+
import { errorToExitCode, EXIT } from "../exit-codes.mjs";
|
|
9
|
+
const pr = z.union([z.number().int().positive(), z.string().url()]).optional();
|
|
10
|
+
const ids = z.array(z.string().min(1)).optional();
|
|
11
|
+
const iterateInputSchema = z.object({
|
|
12
|
+
pr,
|
|
13
|
+
readyDelaySeconds: z.number().nonnegative().optional(),
|
|
14
|
+
stallTimeoutSeconds: z.number().nonnegative().optional(),
|
|
15
|
+
noAutoMarkReady: z.boolean().optional(),
|
|
16
|
+
noAutoCancelActionable: z.boolean().optional(),
|
|
17
|
+
neverCancelRuns: z.array(z.string()).optional(),
|
|
18
|
+
});
|
|
19
|
+
const reviewMutationsOperationSchema = z.object({
|
|
20
|
+
type: z.literal("review_mutations"),
|
|
21
|
+
resolveThreadIds: ids,
|
|
22
|
+
replyThreadIds: ids,
|
|
23
|
+
minimizeCommentIds: ids,
|
|
24
|
+
dismissReviewIds: ids,
|
|
25
|
+
message: z.string().optional(),
|
|
26
|
+
requireSha: z.string().optional(),
|
|
27
|
+
});
|
|
28
|
+
const markFilesViewedOperationSchema = z.object({
|
|
29
|
+
type: z.literal("mark_files_viewed"),
|
|
30
|
+
files: z.array(z.string().min(1)).optional(),
|
|
31
|
+
tests: z.boolean().optional(),
|
|
32
|
+
matchPatterns: z.array(z.string().min(1)).optional(),
|
|
33
|
+
});
|
|
34
|
+
const appendJournalOperationSchema = z.object({
|
|
35
|
+
type: z.literal("append_journal"),
|
|
36
|
+
item: z.string(),
|
|
37
|
+
dryRun: z.boolean().optional(),
|
|
38
|
+
});
|
|
39
|
+
const applyInputSchema = z.object({
|
|
40
|
+
pr,
|
|
41
|
+
operations: z
|
|
42
|
+
.array(z.discriminatedUnion("type", [
|
|
43
|
+
reviewMutationsOperationSchema,
|
|
44
|
+
markFilesViewedOperationSchema,
|
|
45
|
+
appendJournalOperationSchema,
|
|
46
|
+
]))
|
|
47
|
+
.min(1),
|
|
48
|
+
});
|
|
49
|
+
const suggestionPatchInputSchema = z.object({
|
|
50
|
+
pr,
|
|
51
|
+
threadId: z.string().min(1),
|
|
52
|
+
message: z.string().min(1),
|
|
53
|
+
description: z.string().optional(),
|
|
54
|
+
});
|
|
55
|
+
/** Creates a local-only MCP server with the three public Shepherd operations. */
|
|
56
|
+
export function createPrShepherdMcpServer(options = {}) {
|
|
57
|
+
const shepherd = options.shepherd ?? createPrShepherd({ cwd: options.cwd });
|
|
58
|
+
const server = new McpServer({ name: "pr-shepherd", version: readPackageVersion() });
|
|
59
|
+
server.registerTool("iterate", {
|
|
60
|
+
description: "Inspect the current pull request and return the next Shepherd state.",
|
|
61
|
+
inputSchema: iterateInputSchema,
|
|
62
|
+
annotations: {
|
|
63
|
+
readOnlyHint: false,
|
|
64
|
+
destructiveHint: true,
|
|
65
|
+
idempotentHint: false,
|
|
66
|
+
openWorldHint: true,
|
|
67
|
+
},
|
|
68
|
+
}, async (input) => runTool(() => shepherd.iterate(input), formatIterateResult));
|
|
69
|
+
server.registerTool("apply", {
|
|
70
|
+
description: "Apply ordered review, file-view, and journal operations after prevalidation.",
|
|
71
|
+
inputSchema: applyInputSchema,
|
|
72
|
+
annotations: {
|
|
73
|
+
readOnlyHint: false,
|
|
74
|
+
destructiveHint: true,
|
|
75
|
+
idempotentHint: false,
|
|
76
|
+
openWorldHint: true,
|
|
77
|
+
},
|
|
78
|
+
}, async (input) => runTool(() => shepherd.apply(input), formatApplyResult));
|
|
79
|
+
server.registerTool("build_suggestion_patch", {
|
|
80
|
+
description: "Build, but never apply, a patch from an eligible review suggestion.",
|
|
81
|
+
inputSchema: suggestionPatchInputSchema,
|
|
82
|
+
annotations: {
|
|
83
|
+
readOnlyHint: true,
|
|
84
|
+
destructiveHint: false,
|
|
85
|
+
idempotentHint: true,
|
|
86
|
+
openWorldHint: true,
|
|
87
|
+
},
|
|
88
|
+
}, async (input) => runTool(() => shepherd.buildSuggestionPatch(input), formatCommitSuggestionResult));
|
|
89
|
+
return server;
|
|
90
|
+
}
|
|
91
|
+
function readPackageVersion() {
|
|
92
|
+
const packageJson = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
|
|
93
|
+
return packageJson.version;
|
|
94
|
+
}
|
|
95
|
+
function toolResult(result, text) {
|
|
96
|
+
return {
|
|
97
|
+
content: [{ type: "text", text }],
|
|
98
|
+
structuredContent: result,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
async function runTool(work, format) {
|
|
102
|
+
try {
|
|
103
|
+
const result = await work();
|
|
104
|
+
return toolResult(result, format(result));
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
return toolError(error);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function formatApplyResult(result) {
|
|
111
|
+
return result.operations
|
|
112
|
+
.map((operation, index) => {
|
|
113
|
+
const heading = `## Operation ${index + 1}: ${operation.type}`;
|
|
114
|
+
switch (operation.type) {
|
|
115
|
+
case "review_mutations":
|
|
116
|
+
return `${heading}\n\n${formatMutateResult(operation.result)}`;
|
|
117
|
+
case "mark_files_viewed":
|
|
118
|
+
return `${heading}\n\n${formatMarkFilesAsViewedResult(operation.result)}`;
|
|
119
|
+
case "append_journal":
|
|
120
|
+
return `${heading}\n\n${formatJournalResult(operation.result)}`;
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
.join("\n\n");
|
|
124
|
+
}
|
|
125
|
+
function toolError(error) {
|
|
126
|
+
const code = error instanceof PrShepherdValidationError
|
|
127
|
+
? EXIT.USAGE
|
|
128
|
+
: error instanceof PartialApplyError
|
|
129
|
+
? errorToExitCode(error.cause)
|
|
130
|
+
: errorToExitCode(error);
|
|
131
|
+
const details = error instanceof PartialApplyError
|
|
132
|
+
? { failedIndex: error.failedIndex, completed: redactValue(error.completed) }
|
|
133
|
+
: error instanceof PrShepherdValidationError
|
|
134
|
+
? { validation: true }
|
|
135
|
+
: {};
|
|
136
|
+
const message = redactErrorMessage(error instanceof Error ? error.message : String(error));
|
|
137
|
+
return {
|
|
138
|
+
isError: true,
|
|
139
|
+
content: [{ type: "text", text: `pr-shepherd error (${code}): ${message}` }],
|
|
140
|
+
structuredContent: { code, message, details },
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function redactErrorMessage(message) {
|
|
144
|
+
return message
|
|
145
|
+
.replace(/(?:ghp|github_pat)_[A-Za-z0-9_]+/g, "[redacted]")
|
|
146
|
+
.replace(/(authorization:\s*bearer\s+)[^\s]+/gi, "$1[redacted]");
|
|
147
|
+
}
|
|
148
|
+
function redactValue(value) {
|
|
149
|
+
if (typeof value === "string")
|
|
150
|
+
return redactErrorMessage(value);
|
|
151
|
+
if (Array.isArray(value))
|
|
152
|
+
return value.map(redactValue);
|
|
153
|
+
if (value !== null && typeof value === "object") {
|
|
154
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, redactValue(item)]));
|
|
155
|
+
}
|
|
156
|
+
return value;
|
|
157
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runPrShepherdMcpStdio } from "./mcp/index.mjs";
|
|
3
|
+
runPrShepherdMcpStdio().catch((error) => {
|
|
4
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5
|
+
process.stderr.write(`pr-shepherd-mcp error: ${message}\n`);
|
|
6
|
+
process.exitCode = 1;
|
|
7
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derives a shepherd `MergeStatusResult` from raw PR data.
|
|
3
|
+
*
|
|
4
|
+
* `pr.state` is passed through unchanged; `iterate` handles the cancel action
|
|
5
|
+
* for non-OPEN (merged/closed) PRs — this function does not branch on it.
|
|
6
|
+
*
|
|
7
|
+
* Interpretation order for `status` — first match wins:
|
|
8
|
+
* 1. mergeable == CONFLICTING → CONFLICTS (hard conflict even for drafts)
|
|
9
|
+
* 2. blockingBotReviewInProgress → BLOCKED
|
|
10
|
+
* 3. mergeStateStatus DIRTY → CONFLICTS (GitHub merge conflicts, even for drafts)
|
|
11
|
+
* 4. isDraft → DRAFT
|
|
12
|
+
* 5. mergeStateStatus BEHIND → BEHIND
|
|
13
|
+
* 6. mergeStateStatus BLOCKED / HAS_HOOKS → BLOCKED
|
|
14
|
+
* 7. mergeStateStatus UNSTABLE → UNSTABLE
|
|
15
|
+
* 8. mergeStateStatus UNKNOWN → UNKNOWN
|
|
16
|
+
* 9. mergeStateStatus CLEAN → CLEAN
|
|
17
|
+
*/
|
|
18
|
+
import type { BatchPrData, MergeStatusResult } from "../types.mts";
|
|
19
|
+
export declare function deriveMergeStatus(pr: BatchPrData): MergeStatusResult;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Projections for the agent-facing iterate output.
|
|
3
|
+
*
|
|
4
|
+
* These strip fields that are always-false by the time items reach iterate
|
|
5
|
+
* (isResolved, isOutdated, isMinimized, createdAtUnix) and check metadata the
|
|
6
|
+
* agent/iterate prompt never reads (event, status, category).
|
|
7
|
+
* conclusion is preserved on AgentCheck so the formatter can branch on run-level conclusions.
|
|
8
|
+
* detailsUrl is preserved in AgentCheck as a fallback for external status checks.
|
|
9
|
+
* The original domain types are preserved as internal snapshot types.
|
|
10
|
+
*/
|
|
11
|
+
import type { ReviewThread, PrComment, TriagedCheck, ClassifiedCheck, AgentThread, AgentComment, AgentCheck, AgentStalledCheck } from "../types.mts";
|
|
12
|
+
export declare function toAgentThread(t: ReviewThread): AgentThread;
|
|
13
|
+
export declare function toAgentComment(c: PrComment & {
|
|
14
|
+
edited?: boolean;
|
|
15
|
+
}): AgentComment;
|
|
16
|
+
export declare function toAgentCheck(c: TriagedCheck): AgentCheck;
|
|
17
|
+
export declare function toAgentStalledCheck(c: ClassifiedCheck, nowSeconds: number): AgentStalledCheck;
|
|
18
|
+
/**
|
|
19
|
+
* Project failing checks for the agent. Deduplicates only null-runId external
|
|
20
|
+
* checks by name — when runId is present each check may have a distinct job and
|
|
21
|
+
* log tail, so they are all kept.
|
|
22
|
+
*/
|
|
23
|
+
export declare function toAgentChecks(checks: TriagedCheck[]): AgentCheck[];
|
package/bin/reporters/agent.mjs
CHANGED
|
@@ -21,6 +21,7 @@ export function toAgentThread(t) {
|
|
|
21
21
|
t.startLine !== t.line && { startLine: t.startLine }),
|
|
22
22
|
author: t.author,
|
|
23
23
|
...(t.authorType !== undefined && { authorType: t.authorType }),
|
|
24
|
+
...(t.authorAssociation !== undefined && { authorAssociation: t.authorAssociation }),
|
|
24
25
|
body: t.body,
|
|
25
26
|
url: t.url,
|
|
26
27
|
...(t.edited === true && { edited: true }),
|
|
@@ -29,6 +30,7 @@ export function toAgentThread(t) {
|
|
|
29
30
|
id: c.id,
|
|
30
31
|
author: c.author,
|
|
31
32
|
...(c.authorType !== undefined && { authorType: c.authorType }),
|
|
33
|
+
...(c.authorAssociation !== undefined && { authorAssociation: c.authorAssociation }),
|
|
32
34
|
body: c.body,
|
|
33
35
|
url: c.url,
|
|
34
36
|
})),
|
|
@@ -41,6 +43,7 @@ export function toAgentComment(c) {
|
|
|
41
43
|
id: c.id,
|
|
42
44
|
author: c.author,
|
|
43
45
|
...(c.authorType !== undefined && { authorType: c.authorType }),
|
|
46
|
+
...(c.authorAssociation !== undefined && { authorAssociation: c.authorAssociation }),
|
|
44
47
|
body: c.body,
|
|
45
48
|
url: c.url,
|
|
46
49
|
...(c.edited === true && { edited: true }),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolveStateBase(): string;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent first-seen state for bot CHANGES_REQUESTED reviews.
|
|
3
|
+
*
|
|
4
|
+
* Bot CRs are auto-dismissed via `--dismiss-review-ids` in the post-push
|
|
5
|
+
* `apply review:` command. If the agent drops that flag, the bot CR keeps the PR in
|
|
6
|
+
* `CHANGES_REQUESTED` state. This file tracks when each bot CR was first
|
|
7
|
+
* observed so the iterate loop can escalate after `iterate.stallTimeoutMinutes`
|
|
8
|
+
* — independent of the broader fingerprint-based `stall-timeout` mechanism in
|
|
9
|
+
* `iterate-stall.mts` (which only fires when no field of the iterate result
|
|
10
|
+
* changed).
|
|
11
|
+
*
|
|
12
|
+
* State lives in
|
|
13
|
+
* `$TMPDIR/pr-shepherd-state/<owner>-<repo>/<pr>/bot-cr-seen.json`.
|
|
14
|
+
*
|
|
15
|
+
* `bodyHash` lets us reset `firstSeenAt` when the bot re-issues the review
|
|
16
|
+
* with a different body, so a fresh review gets the full timeout window.
|
|
17
|
+
*/
|
|
18
|
+
interface BotCrSeenEntry {
|
|
19
|
+
/** Unix timestamp (seconds) when this review was first observed undismissed. */
|
|
20
|
+
firstSeenAt: number;
|
|
21
|
+
/** SHA-256 body-hash prefix; reset firstSeenAt when this changes. */
|
|
22
|
+
bodyHash: string;
|
|
23
|
+
}
|
|
24
|
+
export interface BotCrSeenState {
|
|
25
|
+
reviews: Record<string, BotCrSeenEntry>;
|
|
26
|
+
}
|
|
27
|
+
interface StateKey {
|
|
28
|
+
owner: string;
|
|
29
|
+
repo: string;
|
|
30
|
+
pr: number;
|
|
31
|
+
}
|
|
32
|
+
export declare function readBotCrSeenState(key: StateKey): Promise<BotCrSeenState | null>;
|
|
33
|
+
export declare function writeBotCrSeenState(key: StateKey, state: BotCrSeenState): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Update tracked entries against the current set of bot CR reviews:
|
|
36
|
+
* - Insert any new IDs with `firstSeenAt = now` and the current body hash.
|
|
37
|
+
* - Reset `firstSeenAt` for entries whose body hash changed (review re-issued).
|
|
38
|
+
* - Drop entries whose IDs are no longer in the input (review was dismissed
|
|
39
|
+
* or superseded by an approval).
|
|
40
|
+
*
|
|
41
|
+
* Pure function: returns the next state and the IDs whose age has reached the
|
|
42
|
+
* `stallTimeoutSeconds` threshold (escalate candidates).
|
|
43
|
+
*/
|
|
44
|
+
export declare function updateBotCrSeenState(previous: BotCrSeenState | null, currentBotCrReviews: ReadonlyArray<{
|
|
45
|
+
id: string;
|
|
46
|
+
body: string;
|
|
47
|
+
}>, nowSeconds: number, stallTimeoutSeconds: number): {
|
|
48
|
+
next: BotCrSeenState;
|
|
49
|
+
staleIds: string[];
|
|
50
|
+
};
|
|
51
|
+
export {};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Persistent first-seen state for bot CHANGES_REQUESTED reviews.
|
|
3
3
|
*
|
|
4
4
|
* Bot CRs are auto-dismissed via `--dismiss-review-ids` in the post-push
|
|
5
|
-
* `
|
|
5
|
+
* `apply review:` command. If the agent drops that flag, the bot CR keeps the PR in
|
|
6
6
|
* `CHANGES_REQUESTED` state. This file tracks when each bot CR was first
|
|
7
7
|
* observed so the iterate loop can escalate after `iterate.stallTimeoutMinutes`
|
|
8
8
|
* — independent of the broader fingerprint-based `stall-timeout` mechanism in
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent attempt counter for the iterate escalation guard.
|
|
3
|
+
*
|
|
4
|
+
* Tracks how many times each review thread has been dispatched to the fix_code
|
|
5
|
+
* handler without being resolved. Counts are reset automatically when the HEAD
|
|
6
|
+
* commit SHA changes (i.e. a new push landed).
|
|
7
|
+
*
|
|
8
|
+
* State lives in `$TMPDIR/pr-shepherd-state/<owner>-<repo>/<pr>/fix-attempts.json`.
|
|
9
|
+
*/
|
|
10
|
+
export interface FixAttemptsState {
|
|
11
|
+
/** HEAD SHA at the time the counts were last written. Reset key. */
|
|
12
|
+
headSha: string;
|
|
13
|
+
/** Map from thread ID → number of fix_code dispatches that included this thread. */
|
|
14
|
+
threadAttempts: Record<string, number>;
|
|
15
|
+
/** Map from thread ID → body hash used for the associated attempt count. */
|
|
16
|
+
threadBodyHashes?: Record<string, string>;
|
|
17
|
+
}
|
|
18
|
+
interface StateKey {
|
|
19
|
+
owner: string;
|
|
20
|
+
repo: string;
|
|
21
|
+
pr: number;
|
|
22
|
+
}
|
|
23
|
+
/** Read the current attempt state. Returns null on miss. */
|
|
24
|
+
export declare function readFixAttempts(key: StateKey): Promise<FixAttemptsState | null>;
|
|
25
|
+
/** Write attempt state (fire-and-forget — never throws). */
|
|
26
|
+
export declare function writeFixAttempts(key: StateKey, state: FixAttemptsState): Promise<void>;
|
|
27
|
+
export {};
|