@deftai/directive-core 0.79.1 → 0.79.3
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/dist/cache/fetch.js +14 -4
- package/dist/capacity/backfill.js +4 -2
- package/dist/category-b-namespace/index.js +31 -6
- package/dist/check/orchestrator.d.ts +4 -0
- package/dist/check/orchestrator.js +9 -1
- package/dist/content-contracts/skills/skill-frontmatter.js +6 -2
- package/dist/eval/health.js +8 -1
- package/dist/eval/run.js +2 -0
- package/dist/fs/projection-containment.d.ts +11 -0
- package/dist/fs/projection-containment.js +62 -1
- package/dist/hooks/dispatcher.d.ts +1 -1
- package/dist/hooks/dispatcher.js +66 -7
- package/dist/init-deposit/hygiene.js +3 -0
- package/dist/intake/reconcile-issues.d.ts +1 -1
- package/dist/intake/reconcile-issues.js +25 -2
- package/dist/issue-sync/sync-from-xbrief-cli.d.ts +1 -0
- package/dist/issue-sync/sync-from-xbrief-cli.js +3 -0
- package/dist/issue-sync/sync-from-xbrief.d.ts +4 -0
- package/dist/issue-sync/sync-from-xbrief.js +12 -0
- package/dist/lifecycle/event.d.ts +15 -0
- package/dist/lifecycle/event.js +163 -0
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.js +1 -0
- package/dist/orchestration/verify-judgment-gates.js +18 -2
- package/dist/platform/resolve-version.d.ts +2 -6
- package/dist/platform/resolve-version.js +3 -129
- package/dist/pr-merge-readiness/compute.d.ts +2 -1
- package/dist/pr-merge-readiness/compute.js +23 -6
- package/dist/pr-merge-readiness/evaluate.d.ts +2 -1
- package/dist/pr-merge-readiness/evaluate.js +46 -31
- package/dist/pr-merge-readiness/gh.js +13 -5
- package/dist/pr-merge-readiness/greptile-inline.d.ts +27 -0
- package/dist/pr-merge-readiness/greptile-inline.js +239 -0
- package/dist/pr-merge-readiness/index.d.ts +1 -0
- package/dist/pr-merge-readiness/index.js +1 -0
- package/dist/pr-merge-readiness/mergeability.d.ts +2 -1
- package/dist/pr-merge-readiness/mergeability.js +7 -1
- package/dist/pr-merge-readiness/test-gh-fixtures.helpers.d.ts +18 -0
- package/dist/pr-merge-readiness/test-gh-fixtures.helpers.js +76 -0
- package/dist/pr-monitor/monitor.js +10 -1
- package/dist/pr-watch/constants.d.ts +5 -0
- package/dist/pr-watch/constants.js +27 -0
- package/dist/pr-watch/index.d.ts +1 -1
- package/dist/pr-watch/index.js +1 -1
- package/dist/pr-watch/main.d.ts +3 -0
- package/dist/pr-watch/main.js +29 -3
- package/dist/release/constants.d.ts +5 -0
- package/dist/release/constants.js +14 -2
- package/dist/release/flags.js +16 -0
- package/dist/release/main.js +7 -0
- package/dist/release/pipeline.js +7 -3
- package/dist/release/preflight.js +8 -1
- package/dist/release/skip-ci-incident.d.ts +22 -0
- package/dist/release/skip-ci-incident.js +68 -0
- package/dist/release/types.d.ts +2 -0
- package/dist/release/version.js +68 -32
- package/dist/release-e2e/entrypoint-worker.js +1 -0
- package/dist/release-e2e/entrypoint.js +13 -0
- package/dist/review-monitor/constants.d.ts +14 -0
- package/dist/review-monitor/constants.js +55 -0
- package/dist/review-monitor/index.d.ts +5 -0
- package/dist/review-monitor/index.js +5 -0
- package/dist/review-monitor/record.d.ts +50 -0
- package/dist/review-monitor/record.js +178 -0
- package/dist/review-monitor/tier-detection.d.ts +15 -0
- package/dist/review-monitor/tier-detection.js +61 -0
- package/dist/review-monitor/verify.d.ts +32 -0
- package/dist/review-monitor/verify.js +171 -0
- package/dist/scope/demote.js +20 -0
- package/dist/swarm/worktrees.d.ts +3 -0
- package/dist/swarm/worktrees.js +18 -2
- package/dist/task-surface/index.js +3 -2
- package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +16 -8
- package/dist/vitest-runner/win32-coverage-tmp-setup.js +41 -14
- package/dist/xbrief-migrate/migration-containment.js +9 -25
- package/package.json +7 -3
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { detect } from "../content-contracts/skills/greptile-detector.js";
|
|
2
|
+
import { GREPTILE_LOGIN } from "./constants.js";
|
|
3
|
+
const REVIEW_THREADS_QUERY = `
|
|
4
|
+
query($owner: String!, $repo: String!, $pr: Int!, $after: String) {
|
|
5
|
+
repository(owner: $owner, name: $repo) {
|
|
6
|
+
pullRequest(number: $pr) {
|
|
7
|
+
reviewThreads(first: 100, after: $after) {
|
|
8
|
+
pageInfo { hasNextPage endCursor }
|
|
9
|
+
nodes {
|
|
10
|
+
isResolved
|
|
11
|
+
isOutdated
|
|
12
|
+
comments(first: 50) {
|
|
13
|
+
nodes {
|
|
14
|
+
author { login }
|
|
15
|
+
body
|
|
16
|
+
path
|
|
17
|
+
commit { oid }
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}`;
|
|
25
|
+
const EMPTY_INLINE = {
|
|
26
|
+
p0Count: 0,
|
|
27
|
+
p1Count: 0,
|
|
28
|
+
unresolvedThreadCount: 0,
|
|
29
|
+
error: null,
|
|
30
|
+
};
|
|
31
|
+
/** True when a review comment commit SHA matches the current PR head (#2620 AC-2). */
|
|
32
|
+
export function headShaMatches(commentSha, headSha) {
|
|
33
|
+
return headSha.startsWith(commentSha) || commentSha.startsWith(headSha);
|
|
34
|
+
}
|
|
35
|
+
export function inlineFindingsToDict(findings) {
|
|
36
|
+
return {
|
|
37
|
+
p0_count: findings.p0Count,
|
|
38
|
+
p1_count: findings.p1Count,
|
|
39
|
+
unresolved_thread_count: findings.unresolvedThreadCount,
|
|
40
|
+
error: findings.error,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function parseOwnerRepo(ownerRepo) {
|
|
44
|
+
const slash = ownerRepo.indexOf("/");
|
|
45
|
+
if (slash <= 0 || slash >= ownerRepo.length - 1) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
return { owner: ownerRepo.slice(0, slash), repo: ownerRepo.slice(slash + 1) };
|
|
49
|
+
}
|
|
50
|
+
function asRecord(value) {
|
|
51
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
function parseReviewComment(node) {
|
|
57
|
+
const record = asRecord(node);
|
|
58
|
+
if (record === null) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const author = asRecord(record.author);
|
|
62
|
+
const login = author?.login;
|
|
63
|
+
if (typeof login !== "string" || login.length === 0) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const body = record.body;
|
|
67
|
+
if (typeof body !== "string") {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const commit = asRecord(record.commit);
|
|
71
|
+
const oid = commit?.oid;
|
|
72
|
+
const path = record.path;
|
|
73
|
+
return {
|
|
74
|
+
authorLogin: login,
|
|
75
|
+
body,
|
|
76
|
+
path: typeof path === "string" ? path : null,
|
|
77
|
+
commitOid: typeof oid === "string" && oid.length > 0 ? oid : null,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function parseReviewThread(node) {
|
|
81
|
+
const record = asRecord(node);
|
|
82
|
+
if (record === null) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const commentsBlock = asRecord(record.comments);
|
|
86
|
+
const commentNodes = commentsBlock?.nodes;
|
|
87
|
+
const comments = [];
|
|
88
|
+
if (Array.isArray(commentNodes)) {
|
|
89
|
+
for (const commentNode of commentNodes) {
|
|
90
|
+
const parsed = parseReviewComment(commentNode);
|
|
91
|
+
if (parsed !== null) {
|
|
92
|
+
comments.push(parsed);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
isResolved: record.isResolved === true,
|
|
98
|
+
isOutdated: record.isOutdated === true,
|
|
99
|
+
comments,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function parseGraphqlReviewThreadsPage(stdout) {
|
|
103
|
+
if (!stdout.trim()) {
|
|
104
|
+
return { threads: [], hasNextPage: false, endCursor: null, error: "empty GraphQL body" };
|
|
105
|
+
}
|
|
106
|
+
let payload;
|
|
107
|
+
try {
|
|
108
|
+
payload = JSON.parse(stdout);
|
|
109
|
+
}
|
|
110
|
+
catch (exc) {
|
|
111
|
+
const message = exc instanceof Error ? exc.message : String(exc);
|
|
112
|
+
return {
|
|
113
|
+
threads: [],
|
|
114
|
+
hasNextPage: false,
|
|
115
|
+
endCursor: null,
|
|
116
|
+
error: `could not parse GraphQL JSON: ${message}`,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const root = asRecord(payload);
|
|
120
|
+
const errors = root?.errors;
|
|
121
|
+
if (Array.isArray(errors) && errors.length > 0) {
|
|
122
|
+
const first = errors[0];
|
|
123
|
+
const message = typeof first === "object" && first !== null && "message" in first
|
|
124
|
+
? String(first.message)
|
|
125
|
+
: "GraphQL errors present";
|
|
126
|
+
return { threads: [], hasNextPage: false, endCursor: null, error: message };
|
|
127
|
+
}
|
|
128
|
+
const data = asRecord(root?.data);
|
|
129
|
+
const repository = asRecord(data?.repository);
|
|
130
|
+
const pullRequest = asRecord(repository?.pullRequest);
|
|
131
|
+
const reviewThreads = asRecord(pullRequest?.reviewThreads);
|
|
132
|
+
const nodes = reviewThreads?.nodes;
|
|
133
|
+
const pageInfo = asRecord(reviewThreads?.pageInfo);
|
|
134
|
+
const threads = [];
|
|
135
|
+
if (Array.isArray(nodes)) {
|
|
136
|
+
for (const node of nodes) {
|
|
137
|
+
const parsed = parseReviewThread(node);
|
|
138
|
+
if (parsed !== null) {
|
|
139
|
+
threads.push(parsed);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
threads,
|
|
145
|
+
hasNextPage: pageInfo?.hasNextPage === true,
|
|
146
|
+
endCursor: typeof pageInfo?.endCursor === "string" ? pageInfo.endCursor : null,
|
|
147
|
+
error: null,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/** Score unresolved Greptile inline P0/P1 threads pinned to the current HEAD (#2620). */
|
|
151
|
+
export function evaluateInlineReviewThreads(threads, headSha) {
|
|
152
|
+
let p0Count = 0;
|
|
153
|
+
let p1Count = 0;
|
|
154
|
+
let unresolvedThreadCount = 0;
|
|
155
|
+
for (const thread of threads) {
|
|
156
|
+
if (thread.isResolved || thread.isOutdated) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
let threadP0 = 0;
|
|
160
|
+
let threadP1 = 0;
|
|
161
|
+
for (const comment of thread.comments) {
|
|
162
|
+
if (comment.authorLogin !== GREPTILE_LOGIN) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (comment.commitOid === null || !headShaMatches(comment.commitOid, headSha)) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const findings = detect(comment.body);
|
|
169
|
+
threadP0 += findings.p0_count;
|
|
170
|
+
threadP1 += findings.p1_count;
|
|
171
|
+
}
|
|
172
|
+
if (threadP0 + threadP1 > 0) {
|
|
173
|
+
p0Count += threadP0;
|
|
174
|
+
p1Count += threadP1;
|
|
175
|
+
unresolvedThreadCount += 1;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return { p0Count, p1Count, unresolvedThreadCount, error: null };
|
|
179
|
+
}
|
|
180
|
+
/** Fetch unresolved Greptile inline P0/P1 on the current HEAD via reviewThreads GraphQL (#2620). */
|
|
181
|
+
export function fetchUnresolvedGreptileInlineFindings(prNumber, repo, headSha, runGh) {
|
|
182
|
+
const parsed = parseOwnerRepo(repo);
|
|
183
|
+
if (parsed === null) {
|
|
184
|
+
return { ...EMPTY_INLINE, error: `invalid repo: ${repo}` };
|
|
185
|
+
}
|
|
186
|
+
const allThreads = [];
|
|
187
|
+
let after = null;
|
|
188
|
+
const maxPages = 10;
|
|
189
|
+
let hasNextPage = true;
|
|
190
|
+
for (let page = 0; hasNextPage && page < maxPages; page += 1) {
|
|
191
|
+
const cmd = [
|
|
192
|
+
"gh",
|
|
193
|
+
"api",
|
|
194
|
+
"graphql",
|
|
195
|
+
"-f",
|
|
196
|
+
`query=${REVIEW_THREADS_QUERY}`,
|
|
197
|
+
"-f",
|
|
198
|
+
`owner=${parsed.owner}`,
|
|
199
|
+
"-f",
|
|
200
|
+
`repo=${parsed.repo}`,
|
|
201
|
+
"-F",
|
|
202
|
+
`pr=${String(prNumber)}`,
|
|
203
|
+
];
|
|
204
|
+
if (after !== null) {
|
|
205
|
+
cmd.push("-f", `after=${after}`);
|
|
206
|
+
}
|
|
207
|
+
const rc = runGh(cmd);
|
|
208
|
+
if (rc.returncode !== 0) {
|
|
209
|
+
return {
|
|
210
|
+
...EMPTY_INLINE,
|
|
211
|
+
error: `graphql reviewThreads failed: ${rc.stderr.trim() || rc.stdout.trim()}`,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const pageResult = parseGraphqlReviewThreadsPage(rc.stdout);
|
|
215
|
+
if (pageResult.error !== null) {
|
|
216
|
+
return { ...EMPTY_INLINE, error: pageResult.error };
|
|
217
|
+
}
|
|
218
|
+
allThreads.push(...pageResult.threads);
|
|
219
|
+
hasNextPage = pageResult.hasNextPage;
|
|
220
|
+
if (!hasNextPage) {
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
if (pageResult.endCursor === null) {
|
|
224
|
+
return {
|
|
225
|
+
...EMPTY_INLINE,
|
|
226
|
+
error: "graphql reviewThreads pagination missing endCursor while hasNextPage=true",
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
after = pageResult.endCursor;
|
|
230
|
+
}
|
|
231
|
+
if (hasNextPage) {
|
|
232
|
+
return {
|
|
233
|
+
...EMPTY_INLINE,
|
|
234
|
+
error: `graphql reviewThreads pagination exceeded ${maxPages} pages`,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
return evaluateInlineReviewThreads(allThreads, headSha);
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=greptile-inline.js.map
|
|
@@ -3,6 +3,7 @@ export { type ComputeGateOptions, computeGateResult, type FetchMergeabilityFn }
|
|
|
3
3
|
export * from "./constants.js";
|
|
4
4
|
export { evaluateGates, isMergeReady } from "./evaluate.js";
|
|
5
5
|
export { defaultRunGh } from "./gh.js";
|
|
6
|
+
export { evaluateInlineReviewThreads, fetchUnresolvedGreptileInlineFindings, headShaMatches, type InlineGreptileFindings, type InlineReviewComment, type InlineReviewThread, inlineFindingsToDict, } from "./greptile-inline.js";
|
|
6
7
|
export { cmdPrMergeReadiness, parseArgs, run } from "./main.js";
|
|
7
8
|
export { fetchMergeability, isGithubMergeableClean, MERGE_STATE_CLEAN, type MergeabilitySignal, mergeabilityToDict, verdictBlockIsSoftOnly, verdictShaIsStale, } from "./mergeability.js";
|
|
8
9
|
export { emitJson, exitCodeFor, gateResultToDict, printHuman } from "./output.js";
|
|
@@ -3,6 +3,7 @@ export { computeGateResult } from "./compute.js";
|
|
|
3
3
|
export * from "./constants.js";
|
|
4
4
|
export { evaluateGates, isMergeReady } from "./evaluate.js";
|
|
5
5
|
export { defaultRunGh } from "./gh.js";
|
|
6
|
+
export { evaluateInlineReviewThreads, fetchUnresolvedGreptileInlineFindings, headShaMatches, inlineFindingsToDict, } from "./greptile-inline.js";
|
|
6
7
|
export { cmdPrMergeReadiness, parseArgs, run } from "./main.js";
|
|
7
8
|
export { fetchMergeability, isGithubMergeableClean, MERGE_STATE_CLEAN, mergeabilityToDict, verdictBlockIsSoftOnly, verdictShaIsStale, } from "./mergeability.js";
|
|
8
9
|
export { emitJson, exitCodeFor, gateResultToDict, printHuman } from "./output.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { InlineGreptileFindings } from "./greptile-inline.js";
|
|
1
2
|
import type { GreptileVerdict, RunGhFn } from "./types.js";
|
|
2
3
|
/**
|
|
3
4
|
* GitHub's authoritative mergeability signal, read over REST (#2260).
|
|
@@ -41,5 +42,5 @@ export declare function verdictShaIsStale(verdict: GreptileVerdict, headSha: str
|
|
|
41
42
|
* regardless of GitHub mergeability (guardrail: do not merge a PR with a real
|
|
42
43
|
* P0/P1 review finding).
|
|
43
44
|
*/
|
|
44
|
-
export declare function verdictBlockIsSoftOnly(verdict: GreptileVerdict, headSha: string | null): boolean;
|
|
45
|
+
export declare function verdictBlockIsSoftOnly(verdict: GreptileVerdict, headSha: string | null, inline?: InlineGreptileFindings | null): boolean;
|
|
45
46
|
//# sourceMappingURL=mergeability.d.ts.map
|
|
@@ -75,7 +75,13 @@ export function verdictShaIsStale(verdict, headSha) {
|
|
|
75
75
|
* regardless of GitHub mergeability (guardrail: do not merge a PR with a real
|
|
76
76
|
* P0/P1 review finding).
|
|
77
77
|
*/
|
|
78
|
-
export function verdictBlockIsSoftOnly(verdict, headSha) {
|
|
78
|
+
export function verdictBlockIsSoftOnly(verdict, headSha, inline = null) {
|
|
79
|
+
if (inline !== null && inline.error !== null) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
if (inline !== null && inline.error === null && (inline.p0Count > 0 || inline.p1Count > 0)) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
79
85
|
// Absent: no Greptile rolling-summary comment at all.
|
|
80
86
|
if (!verdict.found) {
|
|
81
87
|
return true;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test-only gh doubles for pr-monitor / merge-readiness suites (#2652).
|
|
3
|
+
* Not part of the production CLI surface; excluded from coverage via `*.helpers.ts`.
|
|
4
|
+
*/
|
|
5
|
+
import type { RunGhFn } from "./types.js";
|
|
6
|
+
/** Empty GraphQL reviewThreads page for inline Greptile findings (#2620 / #2652). */
|
|
7
|
+
export declare const EMPTY_REVIEW_THREADS_GRAPHQL: string;
|
|
8
|
+
/** Prepend a graphql reviewThreads stub to any hermetic runGh test double. */
|
|
9
|
+
export declare function withGraphqlInlineStub(runGh: RunGhFn): RunGhFn;
|
|
10
|
+
export interface FakeRunGhOptions {
|
|
11
|
+
readonly headSha?: string;
|
|
12
|
+
readonly headOk?: boolean;
|
|
13
|
+
readonly commentsBody?: string;
|
|
14
|
+
readonly commentsOk?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** Hermetic runGh stub for monitor / merge-readiness unit tests (#2260 / #2652). */
|
|
17
|
+
export declare function fakeRunGhForMonitor(options?: FakeRunGhOptions): RunGhFn;
|
|
18
|
+
//# sourceMappingURL=test-gh-fixtures.helpers.d.ts.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/** Empty GraphQL reviewThreads page for inline Greptile findings (#2620 / #2652). */
|
|
2
|
+
export const EMPTY_REVIEW_THREADS_GRAPHQL = JSON.stringify({
|
|
3
|
+
data: {
|
|
4
|
+
repository: {
|
|
5
|
+
pullRequest: {
|
|
6
|
+
reviewThreads: {
|
|
7
|
+
pageInfo: { hasNextPage: false, endCursor: null },
|
|
8
|
+
nodes: [],
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
/** Prepend a graphql reviewThreads stub to any hermetic runGh test double. */
|
|
15
|
+
export function withGraphqlInlineStub(runGh) {
|
|
16
|
+
return (cmd) => {
|
|
17
|
+
if (cmd.join(" ").includes("graphql")) {
|
|
18
|
+
return { returncode: 0, stdout: EMPTY_REVIEW_THREADS_GRAPHQL, stderr: "" };
|
|
19
|
+
}
|
|
20
|
+
return runGh(cmd);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Hermetic runGh stub for monitor / merge-readiness unit tests (#2260 / #2652). */
|
|
24
|
+
export function fakeRunGhForMonitor(options = {}) {
|
|
25
|
+
const headSha = options.headSha ?? "abc1234567890def1234567890abcdef12345678";
|
|
26
|
+
const headOk = options.headOk ?? true;
|
|
27
|
+
const commentsOk = options.commentsOk ?? true;
|
|
28
|
+
const commentsBody = options.commentsBody ?? "";
|
|
29
|
+
return (cmd) => {
|
|
30
|
+
if (!headOk) {
|
|
31
|
+
return { returncode: 1, stdout: "", stderr: "all-down" };
|
|
32
|
+
}
|
|
33
|
+
const joined = cmd.join(" ");
|
|
34
|
+
if (joined.includes("graphql")) {
|
|
35
|
+
return { returncode: 0, stdout: EMPTY_REVIEW_THREADS_GRAPHQL, stderr: "" };
|
|
36
|
+
}
|
|
37
|
+
if (joined.includes("headRefOid")) {
|
|
38
|
+
return { returncode: 0, stdout: `${headSha}\n`, stderr: "" };
|
|
39
|
+
}
|
|
40
|
+
if (joined.includes("/comments")) {
|
|
41
|
+
return commentsOk
|
|
42
|
+
? { returncode: 0, stdout: commentsBody, stderr: "" }
|
|
43
|
+
: { returncode: 1, stdout: "", stderr: "boom" };
|
|
44
|
+
}
|
|
45
|
+
if (joined.includes("/check-runs")) {
|
|
46
|
+
return {
|
|
47
|
+
returncode: 0,
|
|
48
|
+
stdout: JSON.stringify({
|
|
49
|
+
check_runs: [
|
|
50
|
+
{
|
|
51
|
+
name: "TypeScript (build + lint + test)",
|
|
52
|
+
status: "completed",
|
|
53
|
+
conclusion: "success",
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
}),
|
|
57
|
+
stderr: "",
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (joined.includes("/pulls/")) {
|
|
61
|
+
return {
|
|
62
|
+
returncode: 0,
|
|
63
|
+
stdout: JSON.stringify({
|
|
64
|
+
state: "open",
|
|
65
|
+
merged: false,
|
|
66
|
+
mergeable: true,
|
|
67
|
+
mergeable_state: "clean",
|
|
68
|
+
head: { sha: headSha },
|
|
69
|
+
}),
|
|
70
|
+
stderr: "",
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return { returncode: 1, stdout: "", stderr: `unexpected: ${joined}` };
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=test-gh-fixtures.helpers.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cadenceIntervalAfterPoll } from "./cadence.js";
|
|
1
|
+
import { cadenceIntervalAfterPoll, cadenceIntervals } from "./cadence.js";
|
|
2
2
|
import { DEFAULT_CADENCE, EXIT_CAP_REACHED, EXIT_CLEAN, EXIT_CONFIG_ERROR, EXIT_PR_TERMINAL, } from "./constants.js";
|
|
3
3
|
import { callReadiness } from "./readiness.js";
|
|
4
4
|
const systemMonotonicClock = {
|
|
@@ -127,12 +127,21 @@ export function monitor(prNumber, repo, options = {}) {
|
|
|
127
127
|
const sleepFn = options.sleepFn ?? defaultSleep;
|
|
128
128
|
const callReadinessFn = options.callReadinessFn ?? ((n, r) => callReadiness(n, r, { runGh: options.runGh }));
|
|
129
129
|
const startedAt = clockFn.now();
|
|
130
|
+
const intervalSchedule = cadenceIntervals(cadence);
|
|
131
|
+
const minCadenceSec = intervalSchedule.length > 0
|
|
132
|
+
? Math.min(...intervalSchedule)
|
|
133
|
+
: cadenceIntervalAfterPoll(1, cadence);
|
|
134
|
+
// Fail closed when an injected clock never advances (infinite poll loop; #2652).
|
|
135
|
+
const maxPolls = Math.min(10_000, Math.ceil(capSeconds / Math.max(minCadenceSec, 1)) + intervalSchedule.length + 2);
|
|
130
136
|
let pollIndex = 0;
|
|
131
137
|
let lastPayload = {};
|
|
132
138
|
let lastExit = EXIT_CAP_REACHED;
|
|
133
139
|
let priorCadenceSeconds = cadenceIntervalAfterPoll(1, cadence);
|
|
134
140
|
while (true) {
|
|
135
141
|
pollIndex += 1;
|
|
142
|
+
if (pollIndex > maxPolls) {
|
|
143
|
+
return { exitCode: EXIT_CAP_REACHED, payload: lastPayload, pollCount: pollIndex - 1 };
|
|
144
|
+
}
|
|
136
145
|
const elapsed = clockFn.now() - startedAt;
|
|
137
146
|
if (elapsed > capSeconds) {
|
|
138
147
|
return { exitCode: EXIT_CAP_REACHED, payload: lastPayload, pollCount: pollIndex - 1 };
|
|
@@ -23,6 +23,11 @@ export declare const VERDICT_PENDING = "PENDING";
|
|
|
23
23
|
export declare const VERDICT_CONFIG = "CONFIG";
|
|
24
24
|
export declare const DEFAULT_MAX_WAIT_MINUTES = 30;
|
|
25
25
|
export declare const DEFAULT_POLL_SECONDS = 90;
|
|
26
|
+
/**
|
|
27
|
+
* Usage for `task pr:watch -- --help` / `-h` (#2652 / #1056).
|
|
28
|
+
* Canonical surface is the Task verb; engine stem is `pr-watch`.
|
|
29
|
+
*/
|
|
30
|
+
export declare const WATCH_HELP: string;
|
|
26
31
|
/**
|
|
27
32
|
* Consecutive polls with a review PRESENT but stuck on a stale (non-HEAD)
|
|
28
33
|
* commit before the loop surfaces STALL instead of waiting the full cap. Keeps
|
|
@@ -23,6 +23,33 @@ export const VERDICT_PENDING = "PENDING";
|
|
|
23
23
|
export const VERDICT_CONFIG = "CONFIG";
|
|
24
24
|
export const DEFAULT_MAX_WAIT_MINUTES = 30;
|
|
25
25
|
export const DEFAULT_POLL_SECONDS = 90;
|
|
26
|
+
/**
|
|
27
|
+
* Usage for `task pr:watch -- --help` / `-h` (#2652 / #1056).
|
|
28
|
+
* Canonical surface is the Task verb; engine stem is `pr-watch`.
|
|
29
|
+
*/
|
|
30
|
+
export const WATCH_HELP = "usage: task pr:watch -- <pr_number> [options]\n" +
|
|
31
|
+
"\n" +
|
|
32
|
+
"Blocking poll of a PR Greptile/SLizard review to a terminal three-state\n" +
|
|
33
|
+
"verdict (#1056). The invocation IS the wait — an orchestrator that promises\n" +
|
|
34
|
+
"to poll cannot silently forget. Canonical: `task pr:watch -- <N>`.\n" +
|
|
35
|
+
"Engine / CLI stem: `pr-watch` (also `directive pr watch` / `directive pr:watch`).\n" +
|
|
36
|
+
"\n" +
|
|
37
|
+
"positional arguments:\n" +
|
|
38
|
+
" pr_number GitHub pull request number (required unless --help)\n" +
|
|
39
|
+
"\n" +
|
|
40
|
+
"options:\n" +
|
|
41
|
+
" -h, --help Show this help and exit 0\n" +
|
|
42
|
+
" --one-shot Single probe (PENDING with no terminal verdict → exit 2)\n" +
|
|
43
|
+
" --json Emit the AC-4 JSON shape on stdout\n" +
|
|
44
|
+
" --max-wait-minutes N Cap for the blocking poll (default: 30)\n" +
|
|
45
|
+
" --poll-seconds N Seconds between probes (default: 90)\n" +
|
|
46
|
+
" --repo OWNER/REPO Override repository (default: GH_REPO / origin)\n" +
|
|
47
|
+
" --project-root PATH Chdir before probing (optional)\n" +
|
|
48
|
+
"\n" +
|
|
49
|
+
"exit codes:\n" +
|
|
50
|
+
" 0 CLEAN SHA-matched review, confidence > 3, no P0/P1, CI green\n" +
|
|
51
|
+
" 1 NEW_P0_P1 Blocking findings on the current (SHA-matched) review\n" +
|
|
52
|
+
" 2 ERRORED | STALL | TIMEOUT | config / usage error\n";
|
|
26
53
|
/**
|
|
27
54
|
* Consecutive polls with a review PRESENT but stuck on a stale (non-HEAD)
|
|
28
55
|
* commit before the loop surfaces STALL instead of waiting the full cap. Keeps
|
package/dist/pr-watch/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from "./constants.js";
|
|
2
|
-
export { cmdPrWatch, emitWatchJson, type ParsedWatchArgs, parseWatchArgs, printWatchHuman, type RunWatchOptions, runWatch, watchResultToJson, } from "./main.js";
|
|
2
|
+
export { cmdPrWatch, emitWatchJson, formatWatchHelp, type ParsedWatchArgs, parseWatchArgs, printWatchHuman, type RunWatchOptions, runWatch, watchResultToJson, } from "./main.js";
|
|
3
3
|
export { probeOnce } from "./probe.js";
|
|
4
4
|
export * from "./types.js";
|
|
5
5
|
export { formatWatchStatus, watch } from "./watch.js";
|
package/dist/pr-watch/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from "./constants.js";
|
|
2
|
-
export { cmdPrWatch, emitWatchJson, parseWatchArgs, printWatchHuman, runWatch, watchResultToJson, } from "./main.js";
|
|
2
|
+
export { cmdPrWatch, emitWatchJson, formatWatchHelp, parseWatchArgs, printWatchHuman, runWatch, watchResultToJson, } from "./main.js";
|
|
3
3
|
export { probeOnce } from "./probe.js";
|
|
4
4
|
export * from "./types.js";
|
|
5
5
|
export { formatWatchStatus, watch } from "./watch.js";
|
package/dist/pr-watch/main.d.ts
CHANGED
|
@@ -7,9 +7,12 @@ export interface ParsedWatchArgs {
|
|
|
7
7
|
readonly oneShot: boolean;
|
|
8
8
|
readonly emitJson: boolean;
|
|
9
9
|
readonly projectRoot: string | null;
|
|
10
|
+
readonly help: boolean;
|
|
10
11
|
readonly error?: string;
|
|
11
12
|
}
|
|
12
13
|
export declare function parseWatchArgs(argv: readonly string[]): ParsedWatchArgs;
|
|
14
|
+
/** Canonical help text for `task pr:watch -- --help` (#2652). */
|
|
15
|
+
export declare function formatWatchHelp(): string;
|
|
13
16
|
/** AC-4 stable --json shape (same field set as the #1039 Tier-1 instrumentation line). */
|
|
14
17
|
export declare function watchResultToJson(result: WatchResult): Record<string, unknown>;
|
|
15
18
|
export declare function emitWatchJson(result: WatchResult): string;
|
package/dist/pr-watch/main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { defaultRunGh } from "../pr-merge-readiness/gh.js";
|
|
4
|
-
import { DEFAULT_MAX_WAIT_MINUTES, DEFAULT_POLL_SECONDS, EXIT_TERMINAL_ERROR, } from "./constants.js";
|
|
4
|
+
import { DEFAULT_MAX_WAIT_MINUTES, DEFAULT_POLL_SECONDS, EXIT_CLEAN, EXIT_TERMINAL_ERROR, WATCH_HELP, } from "./constants.js";
|
|
5
5
|
import { watch } from "./watch.js";
|
|
6
6
|
function fail(base, error) {
|
|
7
7
|
return { ...base, error };
|
|
@@ -15,6 +15,7 @@ export function parseWatchArgs(argv) {
|
|
|
15
15
|
oneShot: false,
|
|
16
16
|
emitJson: false,
|
|
17
17
|
projectRoot: null,
|
|
18
|
+
help: false,
|
|
18
19
|
};
|
|
19
20
|
let prNumber = null;
|
|
20
21
|
let repo = null;
|
|
@@ -23,6 +24,7 @@ export function parseWatchArgs(argv) {
|
|
|
23
24
|
let oneShot = false;
|
|
24
25
|
let emitJson = false;
|
|
25
26
|
let projectRoot = null;
|
|
27
|
+
let help = false;
|
|
26
28
|
const takePositive = (label, raw) => {
|
|
27
29
|
if (raw === undefined) {
|
|
28
30
|
return { error: `argument ${label}: expected one argument` };
|
|
@@ -35,7 +37,10 @@ export function parseWatchArgs(argv) {
|
|
|
35
37
|
};
|
|
36
38
|
for (let i = 0; i < argv.length; i += 1) {
|
|
37
39
|
const arg = argv[i];
|
|
38
|
-
if (arg === "--
|
|
40
|
+
if (arg === "--help" || arg === "-h") {
|
|
41
|
+
help = true;
|
|
42
|
+
}
|
|
43
|
+
else if (arg === "--json") {
|
|
39
44
|
emitJson = true;
|
|
40
45
|
}
|
|
41
46
|
else if (arg === "--one-shot") {
|
|
@@ -103,10 +108,26 @@ export function parseWatchArgs(argv) {
|
|
|
103
108
|
return fail(acc, `unrecognized arguments: ${arg}`);
|
|
104
109
|
}
|
|
105
110
|
}
|
|
111
|
+
if (help) {
|
|
112
|
+
return { prNumber, repo, maxWaitMinutes, pollSeconds, oneShot, emitJson, projectRoot, help };
|
|
113
|
+
}
|
|
106
114
|
if (prNumber === null) {
|
|
107
115
|
return fail(acc, "the following arguments are required: pr_number");
|
|
108
116
|
}
|
|
109
|
-
return {
|
|
117
|
+
return {
|
|
118
|
+
prNumber,
|
|
119
|
+
repo,
|
|
120
|
+
maxWaitMinutes,
|
|
121
|
+
pollSeconds,
|
|
122
|
+
oneShot,
|
|
123
|
+
emitJson,
|
|
124
|
+
projectRoot,
|
|
125
|
+
help,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/** Canonical help text for `task pr:watch -- --help` (#2652). */
|
|
129
|
+
export function formatWatchHelp() {
|
|
130
|
+
return WATCH_HELP;
|
|
110
131
|
}
|
|
111
132
|
/** Match Python json.dumps(..., indent=2) default ensure_ascii=True. */
|
|
112
133
|
function pythonJsonDumps(value) {
|
|
@@ -161,8 +182,13 @@ export function printWatchHuman(result) {
|
|
|
161
182
|
}
|
|
162
183
|
export function runWatch(argv, options = {}) {
|
|
163
184
|
const args = parseWatchArgs(argv);
|
|
185
|
+
if (args.help) {
|
|
186
|
+
process.stdout.write(formatWatchHelp());
|
|
187
|
+
return EXIT_CLEAN;
|
|
188
|
+
}
|
|
164
189
|
if (args.error !== undefined) {
|
|
165
190
|
process.stderr.write(`pr_watch: ${args.error}\n`);
|
|
191
|
+
process.stderr.write(`Try: task pr:watch -- --help\n`);
|
|
166
192
|
return EXIT_TERMINAL_ERROR;
|
|
167
193
|
}
|
|
168
194
|
let restoreCwd = null;
|
|
@@ -20,6 +20,11 @@ export declare const DESTRUCTIVE_GH_GATE_BYPASS_ENV = "DEFT_ALLOW_DESTRUCTIVE_GH
|
|
|
20
20
|
export declare const RELEASE_PREFLIGHT_ENV = "DEFT_RELEASE_PREFLIGHT";
|
|
21
21
|
/** Set only by release Step-5 preflight when --allow-coverage-debt=#N is supplied (#2573). */
|
|
22
22
|
export declare const COVERAGE_DEBT_ENV = "DEFT_ALLOW_COVERAGE_DEBT";
|
|
23
|
+
/** Hard wall-clock cap for release Step 5 `task check` / vitest coverage (#2652). */
|
|
24
|
+
export declare const RELEASE_CHECK_TIMEOUT_MS: number;
|
|
25
|
+
export declare const RELEASE_CHECK_TIMEOUT_MINUTES = 20;
|
|
26
|
+
/** Vitest coverage step cap in GHA CI (mirrors release Step 5 budget, #2652). */
|
|
27
|
+
export declare const CI_VITEST_COVERAGE_TIMEOUT_MINUTES = 20;
|
|
23
28
|
export declare const PYPROJECT_VERSION_LINE_RE: RegExp;
|
|
24
29
|
/** Byte-identical argparse --help from scripts/release.py (Python 3.12). */
|
|
25
30
|
export declare const RELEASE_HELP: string;
|
|
@@ -28,10 +28,16 @@ export const DESTRUCTIVE_GH_GATE_BYPASS_ENV = "DEFT_ALLOW_DESTRUCTIVE_GH_VERBS";
|
|
|
28
28
|
export const RELEASE_PREFLIGHT_ENV = "DEFT_RELEASE_PREFLIGHT";
|
|
29
29
|
/** Set only by release Step-5 preflight when --allow-coverage-debt=#N is supplied (#2573). */
|
|
30
30
|
export const COVERAGE_DEBT_ENV = "DEFT_ALLOW_COVERAGE_DEBT";
|
|
31
|
+
/** Hard wall-clock cap for release Step 5 `task check` / vitest coverage (#2652). */
|
|
32
|
+
export const RELEASE_CHECK_TIMEOUT_MS = 20 * 60 * 1000;
|
|
33
|
+
export const RELEASE_CHECK_TIMEOUT_MINUTES = 20;
|
|
34
|
+
/** Vitest coverage step cap in GHA CI (mirrors release Step 5 budget, #2652). */
|
|
35
|
+
export const CI_VITEST_COVERAGE_TIMEOUT_MINUTES = 20;
|
|
31
36
|
export const PYPROJECT_VERSION_LINE_RE = /version\s*=\s*"[^"]*"/;
|
|
32
37
|
/** Byte-identical argparse --help from scripts/release.py (Python 3.12). */
|
|
33
38
|
export const RELEASE_HELP = "usage: release [-h] [--dry-run] [--skip-tag] [--skip-release] [--allow-dirty]\n" +
|
|
34
39
|
" [--allow-vbrief-drift] [--allow-coverage-debt #N]\n" +
|
|
40
|
+
" [--allow-skip-ci #N]\n" +
|
|
35
41
|
" [--skip-ci] [--skip-build] [--no-draft]\n" +
|
|
36
42
|
" [--repo OWNER/REPO] [--base-branch BRANCH]\n" +
|
|
37
43
|
" [--project-root PATH] [--summary TEXT]\n" +
|
|
@@ -66,11 +72,17 @@ export const RELEASE_HELP = "usage: release [-h] [--dry-run] [--skip-tag] [--ski
|
|
|
66
72
|
" flag cites an operator-owned issue number (#2573).\n" +
|
|
67
73
|
" PowerShell: use --allow-coverage-debt=N (no bare #)\n" +
|
|
68
74
|
' or quote "#N"; unquoted # starts a comment (#2621).\n' +
|
|
69
|
-
" --skip-ci Skip Step
|
|
75
|
+
" --skip-ci Skip Step 5 (task ci:local / task check fallback).\n" +
|
|
70
76
|
" Used by `task release:e2e` to keep wall-clock\n" +
|
|
71
77
|
" manageable inside the auto-created temp repo (CI\n" +
|
|
72
78
|
" semantics are covered by the unit-test suite, not the\n" +
|
|
73
|
-
" e2e rehearsal)
|
|
79
|
+
" e2e rehearsal). Production cuts MUST pass\n" +
|
|
80
|
+
" --allow-skip-ci=#N citing the tracked incident (#2652);\n" +
|
|
81
|
+
" otherwise Step 5 runs with a hard timeout.\n" +
|
|
82
|
+
" --allow-skip-ci #N Acknowledge skipping Step 5 on a production cut (#2652).\n" +
|
|
83
|
+
" Emits a loud WARN — npm ships without vitest coverage.\n" +
|
|
84
|
+
" PowerShell: use --allow-skip-ci=N (no bare #) or quote\n" +
|
|
85
|
+
' "#N"; unquoted # starts a comment (#2621).\n' +
|
|
74
86
|
" --skip-build Skip Step 6 (task build). Used by `task release:e2e`\n" +
|
|
75
87
|
" to keep wall-clock manageable; build artefacts are not\n" +
|
|
76
88
|
" needed for the draft-release verification step.\n" +
|
package/dist/release/flags.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseCoverageDebtIssueNumber } from "../vitest-runner/coverage-debt.js";
|
|
2
2
|
import { DEFAULT_BASE_BRANCH, RELEASE_HELP } from "./constants.js";
|
|
3
|
+
import { parseSkipCiIncidentArgv } from "./skip-ci-incident.js";
|
|
3
4
|
export function parseReleaseFlags(args) {
|
|
4
5
|
let help = false;
|
|
5
6
|
let dryRun = false;
|
|
@@ -8,6 +9,8 @@ export function parseReleaseFlags(args) {
|
|
|
8
9
|
let allowDirty = false;
|
|
9
10
|
let allowVbriefDrift = false;
|
|
10
11
|
let allowCoverageDebtIssue = null;
|
|
12
|
+
const skipCiIncident = parseSkipCiIncidentArgv(args);
|
|
13
|
+
const allowSkipCiIssue = skipCiIncident.kind === "valid" ? skipCiIncident.issue : null;
|
|
11
14
|
let skipCi = false;
|
|
12
15
|
let skipBuild = false;
|
|
13
16
|
let draft = true;
|
|
@@ -71,6 +74,15 @@ export function parseReleaseFlags(args) {
|
|
|
71
74
|
else if (token === "--skip-ci") {
|
|
72
75
|
skipCi = true;
|
|
73
76
|
}
|
|
77
|
+
else if (token === "--allow-skip-ci") {
|
|
78
|
+
// Value consumed by parseSkipCiIncidentArgv above; advance past it here.
|
|
79
|
+
const value = takeValue(token, i);
|
|
80
|
+
if (value !== null)
|
|
81
|
+
i += 1;
|
|
82
|
+
}
|
|
83
|
+
else if (token.startsWith("--allow-skip-ci=")) {
|
|
84
|
+
// Value consumed by parseSkipCiIncidentArgv above.
|
|
85
|
+
}
|
|
74
86
|
else if (token === "--skip-build") {
|
|
75
87
|
skipBuild = true;
|
|
76
88
|
}
|
|
@@ -132,6 +144,9 @@ export function parseReleaseFlags(args) {
|
|
|
132
144
|
}
|
|
133
145
|
i += 1;
|
|
134
146
|
}
|
|
147
|
+
if (skipCiIncident.kind === "invalid") {
|
|
148
|
+
unknown.push(`--allow-skip-ci (${skipCiIncident.reason})`);
|
|
149
|
+
}
|
|
135
150
|
return {
|
|
136
151
|
help,
|
|
137
152
|
version,
|
|
@@ -144,6 +159,7 @@ export function parseReleaseFlags(args) {
|
|
|
144
159
|
allowDirty,
|
|
145
160
|
allowVbriefDrift,
|
|
146
161
|
allowCoverageDebtIssue,
|
|
162
|
+
allowSkipCiIssue,
|
|
147
163
|
skipCi,
|
|
148
164
|
skipBuild,
|
|
149
165
|
draft,
|