@deftai/directive-core 0.79.2 → 0.79.4
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/check/orchestrator.d.ts +4 -0
- package/dist/check/orchestrator.js +9 -1
- package/dist/coverage-hotspots/evaluate.d.ts +49 -0
- package/dist/coverage-hotspots/evaluate.js +262 -0
- package/dist/coverage-hotspots/index.d.ts +3 -0
- package/dist/coverage-hotspots/index.js +3 -0
- package/dist/coverage-hotspots/thresholds.d.ts +5 -0
- package/dist/coverage-hotspots/thresholds.js +48 -0
- package/dist/hooks/dispatcher.d.ts +7 -0
- package/dist/hooks/dispatcher.js +44 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/init-deposit/xbrief-projections.d.ts +7 -0
- package/dist/init-deposit/xbrief-projections.js +29 -1
- package/dist/integration-e2e/bootstrap-cache-module.d.ts +2 -4
- package/dist/integration-e2e/bootstrap-cache-module.js +2 -20
- package/dist/platform/resolve-version.d.ts +2 -6
- package/dist/platform/resolve-version.js +3 -129
- package/dist/pr-merge-readiness/gh.js +13 -5
- 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-wait-mergeable/cascade.js +8 -1
- package/dist/pr-wait-mergeable/classify.js +5 -1
- package/dist/pr-wait-mergeable/wrappers.js +15 -4
- package/dist/pr-watch/constants.d.ts +15 -0
- package/dist/pr-watch/constants.js +37 -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 +33 -3
- package/dist/pr-watch/probe.js +5 -1
- package/dist/pr-watch/types.d.ts +2 -0
- package/dist/pr-watch/watch.js +17 -1
- 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/undo.js +12 -2
- package/dist/triage/bootstrap/cache-module.d.ts +25 -0
- package/dist/triage/bootstrap/cache-module.js +39 -0
- package/dist/triage/bootstrap/index.d.ts +1 -0
- package/dist/triage/bootstrap/index.js +7 -82
- package/dist/xbrief-migrate/agents-header.d.ts +0 -11
- package/dist/xbrief-migrate/agents-header.js +10 -1
- package/package.json +7 -3
|
@@ -2,113 +2,15 @@ import { execFileSync } from "node:child_process";
|
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { comparePublishableVersions, isPublishable, NonPublishableVersionError, toPep440, } from "../release/version.js";
|
|
5
6
|
import { DEV_FALLBACK, ENV_VAR } from "./constants.js";
|
|
6
|
-
export { DEV_FALLBACK, ENV_VAR };
|
|
7
|
-
export class NonPublishableVersionError extends Error {
|
|
8
|
-
constructor(message) {
|
|
9
|
-
super(message);
|
|
10
|
-
this.name = "NonPublishableVersionError";
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
const PRE_KIND_MAP = {
|
|
14
|
-
alpha: "a",
|
|
15
|
-
beta: "b",
|
|
16
|
-
rc: "rc",
|
|
17
|
-
};
|
|
18
|
-
const NON_PUBLISHABLE_KINDS = new Set(["test"]);
|
|
19
|
-
const PRERELEASE_RANK = {
|
|
20
|
-
alpha: 0,
|
|
21
|
-
beta: 1,
|
|
22
|
-
rc: 2,
|
|
23
|
-
"": 3,
|
|
24
|
-
};
|
|
7
|
+
export { DEV_FALLBACK, ENV_VAR, isPublishable, NonPublishableVersionError, toPep440 };
|
|
25
8
|
function frameworkRoot() {
|
|
26
9
|
const envRoot = process.env.DEFT_ROOT?.trim();
|
|
27
10
|
if (envRoot)
|
|
28
11
|
return resolve(envRoot);
|
|
29
12
|
return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "..");
|
|
30
13
|
}
|
|
31
|
-
/** Parse `[v]X.Y.Z[-(rc|alpha|beta|test).N]` via linear scan. */
|
|
32
|
-
function parsePep440Tag(version) {
|
|
33
|
-
let i = 0;
|
|
34
|
-
if (version[i] === "v" || version[i] === "V")
|
|
35
|
-
i += 1;
|
|
36
|
-
const readInt = () => {
|
|
37
|
-
const ch = version[i] ?? "";
|
|
38
|
-
if (i >= version.length || ch < "0" || ch > "9")
|
|
39
|
-
return null;
|
|
40
|
-
let n = 0;
|
|
41
|
-
while (i < version.length) {
|
|
42
|
-
const digit = version[i] ?? "";
|
|
43
|
-
if (digit < "0" || digit > "9")
|
|
44
|
-
break;
|
|
45
|
-
n = n * 10 + Number(digit);
|
|
46
|
-
i += 1;
|
|
47
|
-
}
|
|
48
|
-
return n;
|
|
49
|
-
};
|
|
50
|
-
const major = readInt();
|
|
51
|
-
if (major === null || version[i] !== ".")
|
|
52
|
-
return null;
|
|
53
|
-
i += 1;
|
|
54
|
-
const minor = readInt();
|
|
55
|
-
if (minor === null || version[i] !== ".")
|
|
56
|
-
return null;
|
|
57
|
-
i += 1;
|
|
58
|
-
const patch = readInt();
|
|
59
|
-
if (patch === null)
|
|
60
|
-
return null;
|
|
61
|
-
if (i >= version.length)
|
|
62
|
-
return { major, minor, patch, kind: null, num: null };
|
|
63
|
-
if (version[i] !== "-")
|
|
64
|
-
return null;
|
|
65
|
-
i += 1;
|
|
66
|
-
const kindStart = i;
|
|
67
|
-
while (i < version.length && version[i] !== ".")
|
|
68
|
-
i += 1;
|
|
69
|
-
if (i >= version.length || version[i] !== ".")
|
|
70
|
-
return null;
|
|
71
|
-
const kind = version.slice(kindStart, i);
|
|
72
|
-
if (!["rc", "alpha", "beta", "test"].includes(kind))
|
|
73
|
-
return null;
|
|
74
|
-
i += 1;
|
|
75
|
-
const num = readInt();
|
|
76
|
-
if (num === null || i !== version.length)
|
|
77
|
-
return null;
|
|
78
|
-
return { major, minor, patch, kind, num };
|
|
79
|
-
}
|
|
80
|
-
export function toPep440(version) {
|
|
81
|
-
if (typeof version !== "string") {
|
|
82
|
-
throw new Error(`version must be a string, got ${typeof version}`);
|
|
83
|
-
}
|
|
84
|
-
const candidate = version.trim();
|
|
85
|
-
if (!candidate)
|
|
86
|
-
throw new Error("version must be a non-empty string");
|
|
87
|
-
const parsed = parsePep440Tag(candidate);
|
|
88
|
-
if (parsed === null) {
|
|
89
|
-
throw new Error(`Cannot normalize ${JSON.stringify(candidate)} to PEP 440: expected [v]X.Y.Z or [v]X.Y.Z-(rc|alpha|beta|test).N`);
|
|
90
|
-
}
|
|
91
|
-
const base = `${parsed.major}.${parsed.minor}.${parsed.patch}`;
|
|
92
|
-
if (parsed.kind === null)
|
|
93
|
-
return base;
|
|
94
|
-
if (NON_PUBLISHABLE_KINDS.has(parsed.kind)) {
|
|
95
|
-
throw new NonPublishableVersionError(`Version ${JSON.stringify(candidate)} carries non-publishable pre-release tag ${JSON.stringify(parsed.kind)}.${parsed.num} -- release pipeline MUST skip pyproject.toml [project].version sync for this tag.`);
|
|
96
|
-
}
|
|
97
|
-
const pepKind = PRE_KIND_MAP[parsed.kind];
|
|
98
|
-
if (pepKind === undefined) {
|
|
99
|
-
throw new Error(`Unmapped pre-release kind ${JSON.stringify(parsed.kind)} for version ${JSON.stringify(candidate)}; add it to PRE_KIND_MAP or NON_PUBLISHABLE_KINDS to keep parser in lockstep with the publishability classifier.`);
|
|
100
|
-
}
|
|
101
|
-
return `${base}${pepKind}${parsed.num}`;
|
|
102
|
-
}
|
|
103
|
-
export function isPublishable(version) {
|
|
104
|
-
try {
|
|
105
|
-
toPep440(version);
|
|
106
|
-
return true;
|
|
107
|
-
}
|
|
108
|
-
catch {
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
14
|
export function tagNameFromRef(ref) {
|
|
113
15
|
let candidate = ref.trim();
|
|
114
16
|
if (!candidate)
|
|
@@ -123,49 +25,21 @@ export function tagNameFromRef(ref) {
|
|
|
123
25
|
candidate = candidate.slice(prefix.length);
|
|
124
26
|
return candidate.trim();
|
|
125
27
|
}
|
|
126
|
-
function publishableTagSortKey(version) {
|
|
127
|
-
const candidate = version.trim();
|
|
128
|
-
const parsed = parsePep440Tag(candidate);
|
|
129
|
-
if (parsed === null) {
|
|
130
|
-
throw new Error(`Cannot sort ${JSON.stringify(candidate)}: expected [v]X.Y.Z or [v]X.Y.Z-(rc|alpha|beta).N`);
|
|
131
|
-
}
|
|
132
|
-
if (parsed.kind !== null && NON_PUBLISHABLE_KINDS.has(parsed.kind)) {
|
|
133
|
-
throw new NonPublishableVersionError(`Version ${JSON.stringify(candidate)} carries non-publishable pre-release tag ${JSON.stringify(parsed.kind)}.`);
|
|
134
|
-
}
|
|
135
|
-
const kind = parsed.kind ?? "";
|
|
136
|
-
const prereleaseRank = PRERELEASE_RANK[kind];
|
|
137
|
-
if (prereleaseRank === undefined) {
|
|
138
|
-
throw new Error(`Unmapped pre-release kind ${JSON.stringify(parsed.kind)} for ${JSON.stringify(candidate)}`);
|
|
139
|
-
}
|
|
140
|
-
return [parsed.major, parsed.minor, parsed.patch, prereleaseRank, parsed.num ?? 0];
|
|
141
|
-
}
|
|
142
28
|
export function latestPublishableTag(tags) {
|
|
143
29
|
let bestTag = null;
|
|
144
|
-
let bestKey = null;
|
|
145
30
|
for (const raw of tags) {
|
|
146
31
|
const tag = tagNameFromRef(raw);
|
|
147
32
|
if (!tag || !isPublishable(tag))
|
|
148
33
|
continue;
|
|
149
34
|
try {
|
|
150
|
-
|
|
151
|
-
if (bestKey === null || compareTuple(key, bestKey) > 0) {
|
|
35
|
+
if (bestTag === null || comparePublishableVersions(tag, bestTag) > 0) {
|
|
152
36
|
bestTag = tag;
|
|
153
|
-
bestKey = key;
|
|
154
37
|
}
|
|
155
38
|
}
|
|
156
39
|
catch { }
|
|
157
40
|
}
|
|
158
41
|
return bestTag;
|
|
159
42
|
}
|
|
160
|
-
function compareTuple(a, b) {
|
|
161
|
-
for (let i = 0; i < a.length; i += 1) {
|
|
162
|
-
const av = a[i] ?? 0;
|
|
163
|
-
const bv = b[i] ?? 0;
|
|
164
|
-
if (av !== bv)
|
|
165
|
-
return av > bv ? 1 : -1;
|
|
166
|
-
}
|
|
167
|
-
return 0;
|
|
168
|
-
}
|
|
169
43
|
function readManifestTag(baseDir) {
|
|
170
44
|
const manifest = join(baseDir, "VERSION");
|
|
171
45
|
try {
|
|
@@ -66,7 +66,10 @@ export function fetchGreptileCommentBody(prNumber, repo, runGh) {
|
|
|
66
66
|
`repos/${resolvedRepo}/issues/${prNumber}/comments`,
|
|
67
67
|
"--paginate",
|
|
68
68
|
"--jq",
|
|
69
|
-
|
|
69
|
+
// Prefer most recently *updated* Greptile summary. Greptile edits the
|
|
70
|
+
// primary rolling summary in place; a later-created duplicate can stay
|
|
71
|
+
// SHA-stale and must not win over the refreshed primary (#1056 / #2658).
|
|
72
|
+
`[.[] | select(.user.login == "${GREPTILE_LOGIN}")] | sort_by(.updated_at) | last | .body // ""`,
|
|
70
73
|
];
|
|
71
74
|
const { returncode, stdout, stderr } = runGh(cmd);
|
|
72
75
|
if (returncode !== 0) {
|
|
@@ -123,7 +126,8 @@ function parsePaginatedComments(text) {
|
|
|
123
126
|
}
|
|
124
127
|
idx = end;
|
|
125
128
|
}
|
|
126
|
-
|
|
129
|
+
let bestBody = "";
|
|
130
|
+
let bestUpdatedAt = "";
|
|
127
131
|
for (const c of comments) {
|
|
128
132
|
if (c !== null &&
|
|
129
133
|
typeof c === "object" &&
|
|
@@ -136,13 +140,17 @@ function parsePaginatedComments(text) {
|
|
|
136
140
|
c.user.login === GREPTILE_LOGIN &&
|
|
137
141
|
"body" in c &&
|
|
138
142
|
typeof c.body === "string") {
|
|
139
|
-
|
|
143
|
+
const updatedAt = "updated_at" in c && typeof c.updated_at === "string" ? c.updated_at : "";
|
|
144
|
+
if (updatedAt >= bestUpdatedAt) {
|
|
145
|
+
bestUpdatedAt = updatedAt;
|
|
146
|
+
bestBody = c.body;
|
|
147
|
+
}
|
|
140
148
|
}
|
|
141
149
|
}
|
|
142
|
-
if (
|
|
150
|
+
if (bestBody.length === 0 && bestUpdatedAt.length === 0) {
|
|
143
151
|
return { body: "", error: "" };
|
|
144
152
|
}
|
|
145
|
-
return { body:
|
|
153
|
+
return { body: bestBody, error: "" };
|
|
146
154
|
}
|
|
147
155
|
/** Mirrors Python json.JSONDecoder.raw_decode for concatenated paginate arrays. */
|
|
148
156
|
class PaginatedJsonDecoder {
|
|
@@ -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 };
|
|
@@ -2,6 +2,13 @@ import { classifyMonitorOutcome, parseMonitorPayload } from "./classify.js";
|
|
|
2
2
|
import { EXIT_CONFIG_ERROR, EXIT_MERGED, EXIT_TIMEOUT_OR_ESCALATION } from "./constants.js";
|
|
3
3
|
import { makeResult } from "./result.js";
|
|
4
4
|
import { runGhMerge, runMonitor, runProtectedCheck } from "./wrappers.js";
|
|
5
|
+
/** Node module-not-found / missing script — not a protected-issue overlap (#2667). */
|
|
6
|
+
function isProtectedCheckConfigFailure(stderr) {
|
|
7
|
+
const tail = stderr.trim();
|
|
8
|
+
return (tail.includes("MODULE_NOT_FOUND") ||
|
|
9
|
+
tail.includes("Cannot find module") ||
|
|
10
|
+
tail.includes("protected-check script not found:"));
|
|
11
|
+
}
|
|
5
12
|
/** Run protected-check -> wait -> merge cascade (#1369). */
|
|
6
13
|
export function waitMergeableAndMerge(prNumber, repo, options) {
|
|
7
14
|
const protectedFn = options.protectedFn ?? runProtectedCheck;
|
|
@@ -17,7 +24,7 @@ export function waitMergeableAndMerge(prNumber, repo, options) {
|
|
|
17
24
|
stderr: prcStderr,
|
|
18
25
|
protected: [...protectedIssues],
|
|
19
26
|
};
|
|
20
|
-
if (prcRc === 1) {
|
|
27
|
+
if (prcRc === 1 && !isProtectedCheckConfigFailure(prcStderr)) {
|
|
21
28
|
return makeResult({
|
|
22
29
|
prNumber,
|
|
23
30
|
repo,
|
|
@@ -5,7 +5,11 @@ export function classifyMonitorOutcome(monitorReturncode, monitorPayload) {
|
|
|
5
5
|
return ["clean", EXIT_MERGED];
|
|
6
6
|
}
|
|
7
7
|
if (monitorReturncode === 1) {
|
|
8
|
-
|
|
8
|
+
// Bare exit 1 (MODULE_NOT_FOUND / missing script) is not budget exhaustion (#2673).
|
|
9
|
+
if (monitorPayload.monitor_result === "CAP-REACHED") {
|
|
10
|
+
return ["cap-reached", EXIT_TIMEOUT_OR_ESCALATION];
|
|
11
|
+
}
|
|
12
|
+
return ["config-error", EXIT_CONFIG_ERROR];
|
|
9
13
|
}
|
|
10
14
|
if (monitorReturncode === 2) {
|
|
11
15
|
return ["config-error", EXIT_CONFIG_ERROR];
|
|
@@ -56,14 +56,17 @@ export function cliScriptPath(name) {
|
|
|
56
56
|
const candidates = [];
|
|
57
57
|
try {
|
|
58
58
|
const require = createRequire(import.meta.url);
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
// package.json is not an exported subpath under @deftai/directive exports (#2667).
|
|
60
|
+
const mainEntry = require.resolve("@deftai/directive");
|
|
61
|
+
candidates.push(resolve(dirname(mainEntry), script));
|
|
61
62
|
}
|
|
62
63
|
catch {
|
|
63
64
|
// Core does not declare a runtime dependency on the CLI package; ignore.
|
|
64
65
|
}
|
|
65
66
|
// Monorepo: packages/core/dist/pr-wait-mergeable -> packages/cli/dist
|
|
66
67
|
candidates.push(resolve(here, "../../../cli/dist", script));
|
|
68
|
+
// npm hoisted: .../@deftai/directive-core/dist/... -> .../@deftai/directive/dist
|
|
69
|
+
candidates.push(resolve(here, "../../../directive/dist", script));
|
|
67
70
|
// npm nested: .../directive/node_modules/@deftai/directive-core/dist/... -> .../directive/dist
|
|
68
71
|
candidates.push(resolve(here, "../../../../dist", script));
|
|
69
72
|
for (const candidate of candidates) {
|
|
@@ -75,9 +78,13 @@ export function cliScriptPath(name) {
|
|
|
75
78
|
}
|
|
76
79
|
/** Invoke pr-protected-issues CLI and return (returncode, stdout, stderr). */
|
|
77
80
|
export function runProtectedCheck(prNumber, repo, protectedIssues, options = {}) {
|
|
81
|
+
const scriptPath = cliScriptPath("pr-protected-issues");
|
|
82
|
+
if (!existsSync(scriptPath)) {
|
|
83
|
+
return [2, "", `protected-check script not found: ${scriptPath}`];
|
|
84
|
+
}
|
|
78
85
|
const node = options.nodeExecutable ?? process.execPath;
|
|
79
86
|
const cmd = [
|
|
80
|
-
|
|
87
|
+
scriptPath,
|
|
81
88
|
String(prNumber),
|
|
82
89
|
"--protected",
|
|
83
90
|
protectedIssues.map(String).join(","),
|
|
@@ -92,10 +99,14 @@ export function runProtectedCheck(prNumber, repo, protectedIssues, options = {})
|
|
|
92
99
|
}
|
|
93
100
|
/** Invoke pr-monitor CLI with --json and return (returncode, stdout, stderr). */
|
|
94
101
|
export function runMonitor(prNumber, repo, capMinutes, options = {}) {
|
|
102
|
+
const scriptPath = cliScriptPath("pr-monitor");
|
|
103
|
+
if (!existsSync(scriptPath)) {
|
|
104
|
+
return [2, "", `monitor script not found: ${scriptPath}`];
|
|
105
|
+
}
|
|
95
106
|
const node = options.nodeExecutable ?? process.execPath;
|
|
96
107
|
const timeoutSec = options.timeout ?? capMinutes * 60 + 60;
|
|
97
108
|
const cmd = [
|
|
98
|
-
|
|
109
|
+
scriptPath,
|
|
99
110
|
String(prNumber),
|
|
100
111
|
"--repo",
|
|
101
112
|
repo,
|
|
@@ -17,12 +17,22 @@ export declare const VERDICT_NEW_P0_P1 = "NEW_P0_P1";
|
|
|
17
17
|
export declare const VERDICT_ERRORED = "ERRORED";
|
|
18
18
|
export declare const VERDICT_STALL = "STALL";
|
|
19
19
|
export declare const VERDICT_TIMEOUT = "TIMEOUT";
|
|
20
|
+
/**
|
|
21
|
+
* Greptile side of the clean gate is satisfied on HEAD but required CI is red
|
|
22
|
+
* (#2688). Exit 2 — fail-loud toward a CI fix loop instead of idle-polling.
|
|
23
|
+
*/
|
|
24
|
+
export declare const VERDICT_CI_BLOCKED = "CI_BLOCKED";
|
|
20
25
|
/** --one-shot only: a single probe with no terminal verdict yet. */
|
|
21
26
|
export declare const VERDICT_PENDING = "PENDING";
|
|
22
27
|
/** External/config fault mid-probe (unresolvable repo/HEAD, gh unavailable). */
|
|
23
28
|
export declare const VERDICT_CONFIG = "CONFIG";
|
|
24
29
|
export declare const DEFAULT_MAX_WAIT_MINUTES = 30;
|
|
25
30
|
export declare const DEFAULT_POLL_SECONDS = 90;
|
|
31
|
+
/**
|
|
32
|
+
* Usage for `task pr:watch -- --help` / `-h` (#2652 / #1056).
|
|
33
|
+
* Canonical surface is the Task verb; engine stem is `pr-watch`.
|
|
34
|
+
*/
|
|
35
|
+
export declare const WATCH_HELP: string;
|
|
26
36
|
/**
|
|
27
37
|
* Consecutive polls with a review PRESENT but stuck on a stale (non-HEAD)
|
|
28
38
|
* commit before the loop surfaces STALL instead of waiting the full cap. Keeps
|
|
@@ -30,4 +40,9 @@ export declare const DEFAULT_POLL_SECONDS = 90;
|
|
|
30
40
|
* whole 30-minute budget (#1039 STALL terminal).
|
|
31
41
|
*/
|
|
32
42
|
export declare const DEFAULT_STALL_THRESHOLD = 3;
|
|
43
|
+
/**
|
|
44
|
+
* Consecutive polls with clean_gate_holdout=ci_failures (Greptile otherwise
|
|
45
|
+
* satisfied) before CI_BLOCKED (#2688). Same default as SHA STALL.
|
|
46
|
+
*/
|
|
47
|
+
export declare const DEFAULT_CI_BLOCKED_THRESHOLD = 3;
|
|
33
48
|
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -17,12 +17,44 @@ export const VERDICT_NEW_P0_P1 = "NEW_P0_P1";
|
|
|
17
17
|
export const VERDICT_ERRORED = "ERRORED";
|
|
18
18
|
export const VERDICT_STALL = "STALL";
|
|
19
19
|
export const VERDICT_TIMEOUT = "TIMEOUT";
|
|
20
|
+
/**
|
|
21
|
+
* Greptile side of the clean gate is satisfied on HEAD but required CI is red
|
|
22
|
+
* (#2688). Exit 2 — fail-loud toward a CI fix loop instead of idle-polling.
|
|
23
|
+
*/
|
|
24
|
+
export const VERDICT_CI_BLOCKED = "CI_BLOCKED";
|
|
20
25
|
/** --one-shot only: a single probe with no terminal verdict yet. */
|
|
21
26
|
export const VERDICT_PENDING = "PENDING";
|
|
22
27
|
/** External/config fault mid-probe (unresolvable repo/HEAD, gh unavailable). */
|
|
23
28
|
export const VERDICT_CONFIG = "CONFIG";
|
|
24
29
|
export const DEFAULT_MAX_WAIT_MINUTES = 30;
|
|
25
30
|
export const DEFAULT_POLL_SECONDS = 90;
|
|
31
|
+
/**
|
|
32
|
+
* Usage for `task pr:watch -- --help` / `-h` (#2652 / #1056).
|
|
33
|
+
* Canonical surface is the Task verb; engine stem is `pr-watch`.
|
|
34
|
+
*/
|
|
35
|
+
export const WATCH_HELP = "usage: task pr:watch -- <pr_number> [options]\n" +
|
|
36
|
+
"\n" +
|
|
37
|
+
"Blocking poll of a PR Greptile/SLizard review to a terminal three-state\n" +
|
|
38
|
+
"verdict (#1056). The invocation IS the wait — an orchestrator that promises\n" +
|
|
39
|
+
"to poll cannot silently forget. Canonical: `task pr:watch -- <N>`.\n" +
|
|
40
|
+
"Engine / CLI stem: `pr-watch` (also `directive pr watch` / `directive pr:watch`).\n" +
|
|
41
|
+
"\n" +
|
|
42
|
+
"positional arguments:\n" +
|
|
43
|
+
" pr_number GitHub pull request number (required unless --help)\n" +
|
|
44
|
+
"\n" +
|
|
45
|
+
"options:\n" +
|
|
46
|
+
" -h, --help Show this help and exit 0\n" +
|
|
47
|
+
" --one-shot Single probe (PENDING with no terminal verdict → exit 2)\n" +
|
|
48
|
+
" --json Emit the AC-4 JSON shape on stdout\n" +
|
|
49
|
+
" --max-wait-minutes N Cap for the blocking poll (default: 30)\n" +
|
|
50
|
+
" --poll-seconds N Seconds between probes (default: 90)\n" +
|
|
51
|
+
" --repo OWNER/REPO Override repository (default: GH_REPO / origin)\n" +
|
|
52
|
+
" --project-root PATH Chdir before probing (optional)\n" +
|
|
53
|
+
"\n" +
|
|
54
|
+
"exit codes:\n" +
|
|
55
|
+
" 0 CLEAN SHA-matched review, confidence > 3, no P0/P1, CI green\n" +
|
|
56
|
+
" 1 NEW_P0_P1 Blocking findings on the current (SHA-matched) review\n" +
|
|
57
|
+
" 2 ERRORED | STALL | TIMEOUT | CI_BLOCKED | config / usage error\n";
|
|
26
58
|
/**
|
|
27
59
|
* Consecutive polls with a review PRESENT but stuck on a stale (non-HEAD)
|
|
28
60
|
* commit before the loop surfaces STALL instead of waiting the full cap. Keeps
|
|
@@ -30,4 +62,9 @@ export const DEFAULT_POLL_SECONDS = 90;
|
|
|
30
62
|
* whole 30-minute budget (#1039 STALL terminal).
|
|
31
63
|
*/
|
|
32
64
|
export const DEFAULT_STALL_THRESHOLD = 3;
|
|
65
|
+
/**
|
|
66
|
+
* Consecutive polls with clean_gate_holdout=ci_failures (Greptile otherwise
|
|
67
|
+
* satisfied) before CI_BLOCKED (#2688). Same default as SHA STALL.
|
|
68
|
+
*/
|
|
69
|
+
export const DEFAULT_CI_BLOCKED_THRESHOLD = 3;
|
|
33
70
|
//# sourceMappingURL=constants.js.map
|
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) {
|
|
@@ -130,6 +151,7 @@ export function watchResultToJson(result) {
|
|
|
130
151
|
p1_count: p.p1Count,
|
|
131
152
|
errored: p.errored,
|
|
132
153
|
ci_failures: p.ciFailures,
|
|
154
|
+
ci_failed_checks: [...p.ciFailedChecks],
|
|
133
155
|
is_clean: p.isClean,
|
|
134
156
|
clean_gate_holdout: p.cleanGateHoldout,
|
|
135
157
|
elapsed_seconds: result.elapsedSeconds,
|
|
@@ -150,6 +172,9 @@ export function printWatchHuman(result) {
|
|
|
150
172
|
lines.push(` Findings: P0=${p.p0Count} P1=${p.p1Count}`);
|
|
151
173
|
lines.push(` Errored sentinel: ${p.errored}`);
|
|
152
174
|
lines.push(` CI failures: ${p.ciFailures}`);
|
|
175
|
+
if (p.ciFailedChecks.length > 0) {
|
|
176
|
+
lines.push(` Failed checks: ${p.ciFailedChecks.join("; ")}`);
|
|
177
|
+
}
|
|
153
178
|
if (p.cleanGateHoldout !== null) {
|
|
154
179
|
lines.push(` Clean-gate holdout: ${p.cleanGateHoldout}`);
|
|
155
180
|
}
|
|
@@ -161,8 +186,13 @@ export function printWatchHuman(result) {
|
|
|
161
186
|
}
|
|
162
187
|
export function runWatch(argv, options = {}) {
|
|
163
188
|
const args = parseWatchArgs(argv);
|
|
189
|
+
if (args.help) {
|
|
190
|
+
process.stdout.write(formatWatchHelp());
|
|
191
|
+
return EXIT_CLEAN;
|
|
192
|
+
}
|
|
164
193
|
if (args.error !== undefined) {
|
|
165
194
|
process.stderr.write(`pr_watch: ${args.error}\n`);
|
|
195
|
+
process.stderr.write(`Try: task pr:watch -- --help\n`);
|
|
166
196
|
return EXIT_TERMINAL_ERROR;
|
|
167
197
|
}
|
|
168
198
|
let restoreCwd = null;
|
package/dist/pr-watch/probe.js
CHANGED
|
@@ -14,6 +14,7 @@ function errorProbe(headSha, message) {
|
|
|
14
14
|
hasBlocking: false,
|
|
15
15
|
errored: false,
|
|
16
16
|
ciFailures: 0,
|
|
17
|
+
ciFailedChecks: [],
|
|
17
18
|
terminalCheckRun: false,
|
|
18
19
|
isClean: false,
|
|
19
20
|
cleanGateHoldout: null,
|
|
@@ -61,12 +62,14 @@ export function probeOnce(prNumber, repoArg, runGh = defaultRunGh) {
|
|
|
61
62
|
// ci_failures=0 / terminal so the Greptile verdict drives; the merge button
|
|
62
63
|
// still owns the hard CI gate via pr:merge-ready (#796).
|
|
63
64
|
let ciFailures = 0;
|
|
65
|
+
let ciFailedChecks = [];
|
|
64
66
|
let terminalCheckRun = true;
|
|
65
67
|
if (repo !== null) {
|
|
66
68
|
const check = fetchCheckRunsRest(headSha, repo, runGh);
|
|
67
69
|
if (check.summary !== null) {
|
|
68
70
|
const ci = evaluateCiGate(check.checkRuns, {});
|
|
69
|
-
|
|
71
|
+
ciFailedChecks = ci.summary.failed_required;
|
|
72
|
+
ciFailures = ciFailedChecks.length;
|
|
70
73
|
terminalCheckRun = ci.summary.pending_required.length === 0;
|
|
71
74
|
}
|
|
72
75
|
}
|
|
@@ -91,6 +94,7 @@ export function probeOnce(prNumber, repoArg, runGh = defaultRunGh) {
|
|
|
91
94
|
hasBlocking: findings.has_blocking,
|
|
92
95
|
errored,
|
|
93
96
|
ciFailures,
|
|
97
|
+
ciFailedChecks,
|
|
94
98
|
terminalCheckRun,
|
|
95
99
|
isClean,
|
|
96
100
|
cleanGateHoldout,
|