pr-shepherd 0.44.1 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +4 -4
- package/bin/checks/conclusions.d.mts +24 -1
- package/bin/checks/conclusions.mjs +39 -0
- package/bin/checks/triage.d.mts +3 -2
- package/bin/checks/triage.mjs +44 -15
- package/bin/cli/api-usage-formatter.d.mts +3 -0
- package/bin/cli/api-usage-formatter.mjs +36 -0
- package/bin/cli/error-format.d.mts +13 -0
- package/bin/cli/error-format.mjs +41 -0
- package/bin/cli/fix-formatter.mjs +12 -1
- package/bin/cli/iterate-activity-formatter.d.mts +2 -0
- package/bin/cli/iterate-activity-formatter.mjs +26 -0
- package/bin/cli/iterate-formatter.mjs +11 -28
- package/bin/cli/iterate-instructions.mjs +14 -1
- package/bin/cli/iterate-lean.mjs +1 -0
- package/bin/cli/mutate-formatter.mjs +2 -0
- package/bin/commands/check-annotations.d.mts +2 -1
- package/bin/commands/check-annotations.mjs +7 -7
- package/bin/commands/check.mjs +6 -4
- package/bin/commands/iterate/api-usage.d.mts +2 -0
- package/bin/commands/iterate/api-usage.mjs +42 -0
- package/bin/commands/iterate/check-instructions.d.mts +2 -2
- package/bin/commands/iterate/check-instructions.mjs +27 -8
- package/bin/commands/iterate/fix-code.mjs +42 -8
- package/bin/commands/iterate/index.mjs +6 -3
- package/bin/commands/iterate/merge-state.mjs +5 -2
- package/bin/commands/iterate/render.d.mts +1 -1
- package/bin/commands/iterate/render.mjs +9 -5
- package/bin/commands/iterate/run.d.mts +2 -0
- package/bin/commands/iterate/run.mjs +8 -0
- package/bin/commands/mark-files-as-viewed.mjs +8 -0
- package/bin/commands/poll-run.d.mts +2 -0
- package/bin/commands/poll-run.mjs +8 -0
- package/bin/commands/poll.d.mts +1 -2
- package/bin/commands/poll.mjs +29 -10
- package/bin/comments/rate-limit.d.mts +4 -0
- package/bin/comments/rate-limit.mjs +6 -0
- package/bin/config/load.d.mts +6 -1
- package/bin/config/load.mjs +33 -1
- package/bin/config.json +6 -1
- package/bin/github/api-telemetry-aggregate.d.mts +32 -0
- package/bin/github/api-telemetry-aggregate.mjs +84 -0
- package/bin/github/api-telemetry.d.mts +13 -0
- package/bin/github/api-telemetry.mjs +128 -0
- package/bin/github/check-annotations.d.mts +14 -1
- package/bin/github/check-annotations.mjs +29 -2
- package/bin/github/errors.d.mts +2 -0
- package/bin/github/errors.mjs +2 -0
- package/bin/github/gql/batch-pr-page.gql +8 -0
- package/bin/github/gql/batch-pr.gql +8 -0
- package/bin/github/gql/check-run-annotations.gql +8 -0
- package/bin/github/gql/commit-check-contexts.gql +8 -0
- package/bin/github/gql/get-pr-body.gql +8 -0
- package/bin/github/gql/get-pr-head-sha.gql +8 -0
- package/bin/github/gql/pr-number-by-branch.gql +8 -0
- package/bin/github/gql/review-thread-comments.gql +8 -0
- package/bin/github/gql/suggestion-threads.gql +8 -0
- package/bin/github/graphql-http.mjs +63 -8
- package/bin/github/http-auth.d.mts +9 -1
- package/bin/github/http-auth.mjs +35 -14
- package/bin/github/http-intermediate.d.mts +9 -0
- package/bin/github/http-intermediate.mjs +20 -0
- package/bin/github/http-request.d.mts +1 -1
- package/bin/github/http-request.mjs +1 -1
- package/bin/github/http-utils.d.mts +6 -0
- package/bin/github/http-utils.mjs +10 -1
- package/bin/github/rest-http.d.mts +16 -1
- package/bin/github/rest-http.mjs +71 -8
- package/bin/github/rest-text.mjs +41 -5
- package/bin/index.mjs +2 -1
- package/bin/log/session.d.mts +11 -0
- package/bin/log/session.mjs +22 -0
- package/bin/mcp/server.mjs +16 -2
- package/bin/quota-warning.d.mts +2 -0
- package/bin/quota-warning.mjs +6 -0
- package/bin/state/graphql-quota-claims.d.mts +2 -0
- package/bin/state/graphql-quota-claims.mjs +61 -0
- package/bin/state/graphql-quota-policy.d.mts +17 -0
- package/bin/state/graphql-quota-policy.mjs +43 -0
- package/bin/state/graphql-quota-warnings.d.mts +6 -0
- package/bin/state/graphql-quota-warnings.mjs +105 -0
- package/bin/state/rest-cache.d.mts +48 -0
- package/bin/state/rest-cache.mjs +91 -0
- package/bin/types/api-usage.d.mts +31 -0
- package/bin/types/api-usage.mjs +1 -0
- package/bin/types/iterate.d.mts +4 -1
- package/bin/types/report.d.mts +2 -0
- package/bin/types.d.mts +1 -0
- package/bin/types.mjs +1 -0
- package/package.json +1 -1
- package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
- package/plugins/pr-shepherd/.codex.mcp.json +1 -1
- package/plugins/pr-shepherd/.mcp.json +1 -1
- package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +14 -9
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { resolveStateBase } from "./base.mjs";
|
|
5
|
+
import { SAFE_SEGMENT } from "../util/path-segment.mjs";
|
|
6
|
+
import { getWorktreeKey } from "../util/worktree.mjs";
|
|
7
|
+
import { claimWarning } from "./graphql-quota-claims.mjs";
|
|
8
|
+
import { evaluateGraphqlQuotaWarning, } from "./graphql-quota-policy.mjs";
|
|
9
|
+
const pendingStateUpdates = new Map();
|
|
10
|
+
const sessionStates = new Map();
|
|
11
|
+
export async function evaluateWorktreeGraphqlQuotaWarning(key, bands, sample, persist, now = Date.now() / 1000) {
|
|
12
|
+
if (bands.length === 0)
|
|
13
|
+
return undefined;
|
|
14
|
+
const path = await warningStatePath(key);
|
|
15
|
+
if (path === undefined) {
|
|
16
|
+
const sessionKey = `${key.owner}/${key.repo}`;
|
|
17
|
+
if (!persist) {
|
|
18
|
+
return evaluateGraphqlQuotaWarning(bands, sample, sessionStates.get(sessionKey) ?? null, now)
|
|
19
|
+
.warning;
|
|
20
|
+
}
|
|
21
|
+
return serializeStateUpdate(`session:${sessionKey}`, async () => {
|
|
22
|
+
const evaluated = evaluateGraphqlQuotaWarning(bands, sample, sessionStates.get(sessionKey) ?? null, now);
|
|
23
|
+
sessionStates.set(sessionKey, evaluated.state);
|
|
24
|
+
return evaluated.warning;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
if (!persist) {
|
|
28
|
+
const previous = await readState(path);
|
|
29
|
+
return evaluateGraphqlQuotaWarning(bands, sample, previous, now).warning;
|
|
30
|
+
}
|
|
31
|
+
return serializeStateUpdate(path, async () => {
|
|
32
|
+
const previous = await readState(path);
|
|
33
|
+
const evaluated = evaluateGraphqlQuotaWarning(bands, sample, previous, now);
|
|
34
|
+
const warning = evaluated.warning !== undefined &&
|
|
35
|
+
(await claimWarning(path, evaluated.warning, now, evaluated.state.rearmEpoch))
|
|
36
|
+
? evaluated.warning
|
|
37
|
+
: undefined;
|
|
38
|
+
await writeState(path, evaluated.state);
|
|
39
|
+
return warning;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async function serializeStateUpdate(key, update) {
|
|
43
|
+
const previous = pendingStateUpdates.get(key) ?? Promise.resolve();
|
|
44
|
+
const result = previous.then(update);
|
|
45
|
+
const completion = result.then(() => undefined, () => undefined);
|
|
46
|
+
pendingStateUpdates.set(key, completion);
|
|
47
|
+
void completion.finally(() => {
|
|
48
|
+
if (pendingStateUpdates.get(key) === completion) {
|
|
49
|
+
pendingStateUpdates.delete(key);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
async function warningStatePath(key) {
|
|
55
|
+
if (!SAFE_SEGMENT.test(key.owner) || !SAFE_SEGMENT.test(key.repo)) {
|
|
56
|
+
throw new Error(`Invalid repo key segments: ${key.owner}/${key.repo}`);
|
|
57
|
+
}
|
|
58
|
+
let worktreeKey;
|
|
59
|
+
try {
|
|
60
|
+
worktreeKey = await getWorktreeKey();
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
return join(resolveStateBase(), `${key.owner}-${key.repo}`, "worktrees", `${worktreeKey}-graphql-quota-warnings.json`);
|
|
66
|
+
}
|
|
67
|
+
async function readState(path) {
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
70
|
+
if (typeof parsed.resource !== "string" ||
|
|
71
|
+
!Number.isFinite(parsed.limit) ||
|
|
72
|
+
!Number.isFinite(parsed.lastRemaining) ||
|
|
73
|
+
!Number.isFinite(parsed.resetAt) ||
|
|
74
|
+
!Array.isArray(parsed.warnedThresholds)) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return parsed;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function writeState(path, state) {
|
|
84
|
+
let tmp;
|
|
85
|
+
try {
|
|
86
|
+
await mkdir(dirname(path), { recursive: true });
|
|
87
|
+
tmp = `${path}.${randomUUID()}.tmp`;
|
|
88
|
+
await writeFile(tmp, JSON.stringify(state), "utf8");
|
|
89
|
+
await rename(tmp, path);
|
|
90
|
+
tmp = undefined;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Best-effort warning state must never fail the command.
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
if (tmp !== undefined) {
|
|
97
|
+
try {
|
|
98
|
+
await unlink(tmp);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Best-effort cleanup.
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-tick GitHub API response cache — reduces redundant requests across
|
|
3
|
+
* `pr-shepherd` poll ticks.
|
|
4
|
+
*
|
|
5
|
+
* Two entry kinds, both keyed by a caller-chosen logical `name` (hashed to a
|
|
6
|
+
* filesystem-safe filename, mirroring seen-comments.mts):
|
|
7
|
+
*
|
|
8
|
+
* - "etag" entries back conditional REST requests (`If-None-Match`). A 200
|
|
9
|
+
* response overwrites the entry; a 304 leaves it untouched. Used for list
|
|
10
|
+
* endpoints whose content can change from tick to tick (job lists, run
|
|
11
|
+
* lists).
|
|
12
|
+
* - "derived" entries cache a value computed from a response that is
|
|
13
|
+
* immutable once identified by its key — job-log excerpts once a job is
|
|
14
|
+
* terminal, check-run annotations once the check is COMPLETED. No ETag
|
|
15
|
+
* applies; the cache is keyed on an immutable identity instead.
|
|
16
|
+
*
|
|
17
|
+
* Entries live under `$PR_SHEPHERD_STATE_DIR/<owner>-<repo>/<pr>/rest-cache/`
|
|
18
|
+
* and are removed for free when `pr-shepherd clean` deletes the PR's state
|
|
19
|
+
* directory — there is no separate pruning routine.
|
|
20
|
+
*/
|
|
21
|
+
export interface StateKey {
|
|
22
|
+
owner: string;
|
|
23
|
+
repo: string;
|
|
24
|
+
pr: number;
|
|
25
|
+
}
|
|
26
|
+
export interface EtagCacheEntry {
|
|
27
|
+
kind: "etag";
|
|
28
|
+
name: string;
|
|
29
|
+
etag: string;
|
|
30
|
+
body: unknown;
|
|
31
|
+
storedAt: number;
|
|
32
|
+
headSha?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface DerivedCacheEntry<T = unknown> {
|
|
35
|
+
kind: "derived";
|
|
36
|
+
name: string;
|
|
37
|
+
value: T;
|
|
38
|
+
storedAt: number;
|
|
39
|
+
headSha?: string;
|
|
40
|
+
}
|
|
41
|
+
export declare function loadEtagEntry(key: StateKey, name: string): Promise<EtagCacheEntry | null>;
|
|
42
|
+
export declare function storeEtagEntry(key: StateKey, name: string, fields: {
|
|
43
|
+
etag: string;
|
|
44
|
+
body: unknown;
|
|
45
|
+
headSha?: string;
|
|
46
|
+
}): Promise<void>;
|
|
47
|
+
export declare function loadDerived<T = unknown>(key: StateKey, name: string): Promise<DerivedCacheEntry<T> | null>;
|
|
48
|
+
export declare function storeDerived<T = unknown>(key: StateKey, name: string, value: T, headSha?: string): Promise<void>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-tick GitHub API response cache — reduces redundant requests across
|
|
3
|
+
* `pr-shepherd` poll ticks.
|
|
4
|
+
*
|
|
5
|
+
* Two entry kinds, both keyed by a caller-chosen logical `name` (hashed to a
|
|
6
|
+
* filesystem-safe filename, mirroring seen-comments.mts):
|
|
7
|
+
*
|
|
8
|
+
* - "etag" entries back conditional REST requests (`If-None-Match`). A 200
|
|
9
|
+
* response overwrites the entry; a 304 leaves it untouched. Used for list
|
|
10
|
+
* endpoints whose content can change from tick to tick (job lists, run
|
|
11
|
+
* lists).
|
|
12
|
+
* - "derived" entries cache a value computed from a response that is
|
|
13
|
+
* immutable once identified by its key — job-log excerpts once a job is
|
|
14
|
+
* terminal, check-run annotations once the check is COMPLETED. No ETag
|
|
15
|
+
* applies; the cache is keyed on an immutable identity instead.
|
|
16
|
+
*
|
|
17
|
+
* Entries live under `$PR_SHEPHERD_STATE_DIR/<owner>-<repo>/<pr>/rest-cache/`
|
|
18
|
+
* and are removed for free when `pr-shepherd clean` deletes the PR's state
|
|
19
|
+
* directory — there is no separate pruning routine.
|
|
20
|
+
*/
|
|
21
|
+
import { readFile, writeFile, rename, unlink, mkdir } from "node:fs/promises";
|
|
22
|
+
import { dirname } from "node:path";
|
|
23
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
24
|
+
import { resolvePrStatePath } from "./base.mjs";
|
|
25
|
+
export async function loadEtagEntry(key, name) {
|
|
26
|
+
const entry = await readEntry(key, name);
|
|
27
|
+
return entry?.kind === "etag" ? entry : null;
|
|
28
|
+
}
|
|
29
|
+
export async function storeEtagEntry(key, name, fields) {
|
|
30
|
+
await writeEntry(key, name, {
|
|
31
|
+
kind: "etag",
|
|
32
|
+
name,
|
|
33
|
+
etag: fields.etag,
|
|
34
|
+
body: fields.body,
|
|
35
|
+
storedAt: Date.now(),
|
|
36
|
+
...(fields.headSha !== undefined && { headSha: fields.headSha }),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
export async function loadDerived(key, name) {
|
|
40
|
+
const entry = await readEntry(key, name);
|
|
41
|
+
return entry?.kind === "derived" ? entry : null;
|
|
42
|
+
}
|
|
43
|
+
export async function storeDerived(key, name, value, headSha) {
|
|
44
|
+
await writeEntry(key, name, {
|
|
45
|
+
kind: "derived",
|
|
46
|
+
name,
|
|
47
|
+
value,
|
|
48
|
+
storedAt: Date.now(),
|
|
49
|
+
...(headSha !== undefined && { headSha }),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Helpers
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
async function readEntry(key, name) {
|
|
56
|
+
try {
|
|
57
|
+
const raw = await readFile(resolvePath(key, name), "utf8");
|
|
58
|
+
return JSON.parse(raw);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function writeEntry(key, name, entry) {
|
|
65
|
+
let tmp;
|
|
66
|
+
try {
|
|
67
|
+
const path = resolvePath(key, name);
|
|
68
|
+
await mkdir(dirname(path), { recursive: true });
|
|
69
|
+
tmp = `${path}.${randomUUID()}.tmp`;
|
|
70
|
+
await writeFile(tmp, JSON.stringify(entry), "utf8");
|
|
71
|
+
await rename(tmp, path);
|
|
72
|
+
tmp = undefined;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// best-effort — a failed cache write just means the next tick re-fetches
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
if (tmp !== undefined) {
|
|
79
|
+
try {
|
|
80
|
+
await unlink(tmp);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// best-effort cleanup
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function resolvePath(key, name) {
|
|
89
|
+
const hash = createHash("sha256").update(name, "utf8").digest("hex");
|
|
90
|
+
return resolvePrStatePath(key, "rest-cache", `${hash}.json`);
|
|
91
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface ApiResourceUsage {
|
|
2
|
+
resource: string;
|
|
3
|
+
requestCount: number;
|
|
4
|
+
limit: number;
|
|
5
|
+
used?: number;
|
|
6
|
+
remaining: number;
|
|
7
|
+
resetAt: number;
|
|
8
|
+
}
|
|
9
|
+
export interface GraphqlApiUsage extends ApiResourceUsage {
|
|
10
|
+
/** Exact sum reported by rateLimit.cost for GraphQL queries in this command. */
|
|
11
|
+
measuredQueryCost: number;
|
|
12
|
+
/** Requests without an exact cost, principally GraphQL mutations. */
|
|
13
|
+
unmeasuredRequestCount: number;
|
|
14
|
+
/** Exact sum reported by rateLimit.nodeCount for measured GraphQL queries. */
|
|
15
|
+
nodeCount: number;
|
|
16
|
+
}
|
|
17
|
+
export interface ApiUsage {
|
|
18
|
+
credentialSources: string[];
|
|
19
|
+
graphql?: GraphqlApiUsage;
|
|
20
|
+
rest?: ApiResourceUsage[];
|
|
21
|
+
}
|
|
22
|
+
export interface GraphqlQuotaWarning {
|
|
23
|
+
resource: "graphql";
|
|
24
|
+
thresholdPercent: number;
|
|
25
|
+
remaining: number;
|
|
26
|
+
limit: number;
|
|
27
|
+
used?: number;
|
|
28
|
+
resetAt: number;
|
|
29
|
+
pollIntervalMinutes: number;
|
|
30
|
+
pollTimeoutMinutes: number;
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/bin/types/iterate.d.mts
CHANGED
|
@@ -5,6 +5,7 @@ import type { MergeRequirements } from "./merge-requirements.mts";
|
|
|
5
5
|
import type { EscalateDetails } from "./escalate.mts";
|
|
6
6
|
import type { MergeCommandPlan } from "./merge-action.mts";
|
|
7
7
|
import type { ProtectedRun } from "./protected-run.mts";
|
|
8
|
+
import type { ApiUsage, GraphqlQuotaWarning } from "./api-usage.mts";
|
|
8
9
|
export type ShepherdAction = "wait" | "fix_code" | "mark_ready" | "merge" | "cancel" | "escalate";
|
|
9
10
|
export interface IterateResultSummary {
|
|
10
11
|
passing: number;
|
|
@@ -41,6 +42,8 @@ export interface IterateResultBase {
|
|
|
41
42
|
supersededNames?: string[];
|
|
42
43
|
activity?: PrActivitySummary;
|
|
43
44
|
mergeQueue?: import("./merge-queue.mts").MergeQueueReport;
|
|
45
|
+
apiUsage?: ApiUsage;
|
|
46
|
+
quotaWarning?: GraphqlQuotaWarning;
|
|
44
47
|
}
|
|
45
48
|
interface IterateResultWait extends IterateResultBase {
|
|
46
49
|
action: "wait";
|
|
@@ -129,12 +132,12 @@ export interface IterateCommandOptions extends GlobalOptions {
|
|
|
129
132
|
noAutoMarkReady?: boolean;
|
|
130
133
|
/** Legacy no-op retained for API compatibility; workflow runs are never cancelled. */
|
|
131
134
|
noAutoCancelActionable?: boolean;
|
|
132
|
-
/** Override stall timeout seconds. Defaults to config.iterate.stallTimeoutMinutes * 60. */
|
|
133
135
|
stallTimeoutSeconds?: number;
|
|
134
136
|
/** Legacy per-invocation no-op retained for API compatibility. */
|
|
135
137
|
neverCancelRuns?: string[];
|
|
136
138
|
persistSeen?: boolean;
|
|
137
139
|
/** Shepherd through readiness and emit the exact merge/queue command when ready. */
|
|
138
140
|
merge?: boolean;
|
|
141
|
+
deferQuotaWarning?: boolean;
|
|
139
142
|
}
|
|
140
143
|
export {};
|
package/bin/types/report.d.mts
CHANGED
|
@@ -136,6 +136,8 @@ export interface AgentCheck {
|
|
|
136
136
|
/** One-line status text shown in the GitHub UI (e.g. "67.68% of diff hit (target 85.00%)"). */
|
|
137
137
|
summary?: string;
|
|
138
138
|
logExcerpt?: string;
|
|
139
|
+
/** `gh run rerun` command, present only when the check has a runId and the viewer's repository role grants Actions rerun capability (WRITE+). */
|
|
140
|
+
rerunCommand?: string;
|
|
139
141
|
annotations?: CheckAnnotation[];
|
|
140
142
|
annotationOnly?: true;
|
|
141
143
|
scope?: "merge_group";
|
package/bin/types.d.mts
CHANGED
package/bin/types.mjs
CHANGED
package/package.json
CHANGED
|
@@ -28,7 +28,7 @@ mechanics every tick. Apply the referenced playbook in full whenever a step poin
|
|
|
28
28
|
### Suggestion patches
|
|
29
29
|
|
|
30
30
|
- Run one plural `build-suggestion-patches` command with a repeated `--thread-id … --message … [--description …]` group for every marked thread in displayed order.
|
|
31
|
-
- The CLI only builds patches. Apply, stage, and commit the returned patches in order.
|
|
31
|
+
- The CLI only builds patches. Apply, stage, and commit the returned patches in order. This command does not itself check push authorization — follow the `iterate`/`fix_code` output's own commit/push instruction for whether to push now or hand off: it reflects whether GitHub confirms the viewer can push to the PR head branch.
|
|
32
32
|
- The command builds from the fetched PR head and accepts a clean local descendant only when the complete ordered patch stream passes `git apply --check`.
|
|
33
33
|
- If the command refuses because a suggestion is unsafe or no longer applies, inspect the current source, the displayed replacement block, and reviewer intent before editing manually. Do not apply a stale numeric range blindly or retry unchanged input.
|
|
34
34
|
- A returned patch was checked against the then-current worktree. If it later fails, re-inspect the worktree because it changed after validation.
|
|
@@ -40,14 +40,19 @@ Match each failure's `[conclusion: …]` tag under `## Failing checks` to a rule
|
|
|
40
40
|
|
|
41
41
|
More specific rows win over the general "GitHub Actions failure" row — check conclusion first.
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
| `
|
|
50
|
-
|
|
|
43
|
+
A `[rerun authorized]` tag with a `rerun:` command means the viewer's repository role grants GitHub's Actions rerun capability (WRITE+) — Shepherd verified this from `repositoryPermission`. This confirms the account's role, not the granular scope of whatever credential actually runs `gh`; if the printed `rerun:` command still fails when run, do not retry it — hand off the displayed metadata instead, the same as an unauthorized check. No tag/command means Shepherd could not verify the account-level capability at all; hand off instead of guessing. A run still in progress, an `ACTION_REQUIRED` run (paused pending manual workflow approval — a rerun cannot grant that approval), or a check whose runId does not resolve to a GitHub Actions workflow never gets `[rerun authorized]`, regardless of role.
|
|
44
|
+
|
|
45
|
+
When several bullets share one runId (matrix jobs from the same run), the `rerun:` command is printed once, on the first bullet; every bullet for that runId still carries `[rerun authorized]` and is covered by that single command — do not run it more than once.
|
|
46
|
+
|
|
47
|
+
| Tag / kind | Do |
|
|
48
|
+
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
49
|
+
| GitHub Actions failure (has a run ID, not `CANCELLED`/`STARTUP_FAILURE`) | Read the included log excerpt if one is rendered. If it is missing or insufficient: run the `rerun:` command if `[rerun authorized]`, then iterate again once it completes; otherwise hand off the displayed run ID/URL. |
|
|
50
|
+
| Transient infrastructure failure | Run the `rerun:` command if `[rerun authorized]`, then iterate again once it completes. Otherwise record the diagnosis and hand off — Shepherd cannot verify rerun authorization. |
|
|
51
|
+
| Real test or build failure | Apply a code fix — do not rerun, even if `[rerun authorized]` is shown. |
|
|
52
|
+
| `[conclusion: CANCELLED]` | No log excerpt is rendered. Run the `rerun:` command if `[rerun authorized]`, then iterate again once it completes; otherwise hand off the displayed metadata. |
|
|
53
|
+
| `[conclusion: STARTUP_FAILURE]` | No log excerpt is rendered. Run the `rerun:` command if `[rerun authorized]`, then iterate again once it completes; otherwise hand off the displayed metadata. |
|
|
54
|
+
| `[conclusion: ACTION_REQUIRED]` | Never carries `[rerun authorized]`. Hand off — the run needs a maintainer's manual workflow approval on GitHub, which a rerun cannot grant. |
|
|
55
|
+
| `external` (no run ID, has a URL) | Preserve the URL in the handoff. Shepherd does not recommend opening it because it cannot verify the current viewer's access to the external system. |
|
|
51
56
|
|
|
52
57
|
### Review-mutation mechanics
|
|
53
58
|
|