@deftai/directive-core 0.66.2 → 0.68.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/dist/agents-md-advisory/evaluate.d.ts +44 -0
- package/dist/agents-md-advisory/evaluate.js +115 -0
- package/dist/agents-md-advisory/index.d.ts +2 -0
- package/dist/agents-md-advisory/index.js +2 -0
- package/dist/agents-md-budget/evaluate.d.ts +38 -0
- package/dist/agents-md-budget/evaluate.js +152 -0
- package/dist/agents-md-budget/index.d.ts +2 -0
- package/dist/agents-md-budget/index.js +2 -0
- package/dist/check/index.d.ts +1 -1
- package/dist/check/index.js +1 -1
- package/dist/check/orchestrator.d.ts +14 -4
- package/dist/check/orchestrator.js +30 -6
- package/dist/codebase/map.d.ts +1 -0
- package/dist/codebase/map.js +5 -2
- package/dist/doctor/checks.js +11 -11
- package/dist/doctor/constants.d.ts +7 -2
- package/dist/doctor/constants.js +17 -1
- package/dist/doctor/flags.js +5 -1
- package/dist/doctor/main.js +99 -8
- package/dist/doctor/payload-staleness.js +15 -6
- package/dist/doctor/types.d.ts +7 -0
- package/dist/forward-coverage/evaluate.d.ts +55 -0
- package/dist/forward-coverage/evaluate.js +254 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/init-deposit/constants.d.ts +14 -0
- package/dist/init-deposit/constants.js +10 -0
- package/dist/init-deposit/hygiene.d.ts +50 -0
- package/dist/init-deposit/hygiene.js +176 -0
- package/dist/init-deposit/init-deposit.d.ts +1 -0
- package/dist/init-deposit/init-deposit.js +7 -1
- package/dist/init-deposit/refresh.d.ts +2 -0
- package/dist/init-deposit/refresh.js +48 -30
- package/dist/init-deposit/scaffold.d.ts +3 -4
- package/dist/init-deposit/scaffold.js +3 -24
- package/dist/intake/github-auth-modes.d.ts +1 -0
- package/dist/intake/github-auth-modes.js +1 -0
- package/dist/intake/issue-ingest.d.ts +6 -2
- package/dist/intake/issue-ingest.js +65 -9
- package/dist/integration-e2e/helpers.d.ts +0 -1
- package/dist/integration-e2e/helpers.js +3 -7
- package/dist/layout/lifecycle-ref.d.ts +15 -0
- package/dist/layout/lifecycle-ref.js +55 -0
- package/dist/platform/agents-consumer-header.d.ts +13 -0
- package/dist/platform/agents-consumer-header.js +57 -0
- package/dist/platform/agents-md.d.ts +28 -0
- package/dist/platform/agents-md.js +38 -1
- package/dist/platform/index.d.ts +1 -0
- package/dist/platform/index.js +1 -0
- package/dist/policy/agents-md-advisory.d.ts +52 -0
- package/dist/policy/agents-md-advisory.js +63 -0
- package/dist/policy/agents-md-budget.d.ts +24 -0
- package/dist/policy/agents-md-budget.js +89 -0
- package/dist/policy/index.d.ts +1 -0
- package/dist/policy/index.js +1 -0
- package/dist/policy/resolve.js +56 -46
- package/dist/pr-merge-readiness/ci-gate.d.ts +28 -0
- package/dist/pr-merge-readiness/ci-gate.js +79 -0
- package/dist/pr-merge-readiness/compute.d.ts +15 -1
- package/dist/pr-merge-readiness/compute.js +129 -9
- package/dist/pr-merge-readiness/gh.d.ts +8 -0
- package/dist/pr-merge-readiness/gh.js +30 -5
- package/dist/pr-merge-readiness/index.d.ts +5 -1
- package/dist/pr-merge-readiness/index.js +3 -0
- package/dist/pr-merge-readiness/main.d.ts +3 -0
- package/dist/pr-merge-readiness/main.js +73 -6
- package/dist/pr-merge-readiness/mergeability.d.ts +43 -0
- package/dist/pr-merge-readiness/mergeability.js +105 -0
- package/dist/pr-merge-readiness/output.js +20 -0
- package/dist/pr-merge-readiness/slizard-gate.d.ts +47 -0
- package/dist/pr-merge-readiness/slizard-gate.js +119 -0
- package/dist/pr-monitor/index.d.ts +1 -1
- package/dist/pr-monitor/index.js +1 -1
- package/dist/pr-monitor/monitor.d.ts +8 -2
- package/dist/pr-monitor/monitor.js +35 -6
- package/dist/pr-wait-mergeable/wrappers.d.ts +12 -1
- package/dist/pr-wait-mergeable/wrappers.js +13 -5
- package/dist/pr-watch/constants.d.ts +33 -0
- package/dist/pr-watch/constants.js +33 -0
- package/dist/pr-watch/index.d.ts +6 -0
- package/dist/pr-watch/index.js +6 -0
- package/dist/pr-watch/main.d.ts +21 -0
- package/dist/pr-watch/main.js +206 -0
- package/dist/pr-watch/probe.d.ts +13 -0
- package/dist/pr-watch/probe.js +100 -0
- package/dist/pr-watch/types.d.ts +47 -0
- package/dist/pr-watch/types.js +2 -0
- package/dist/pr-watch/watch.d.ts +12 -0
- package/dist/pr-watch/watch.js +122 -0
- package/dist/preflight-cache/evaluate.d.ts +1 -0
- package/dist/preflight-cache/evaluate.js +17 -10
- package/dist/render/project-render.d.ts +16 -2
- package/dist/render/project-render.js +155 -97
- package/dist/scope/capacity-stamp.d.ts +24 -2
- package/dist/scope/capacity-stamp.js +80 -28
- package/dist/scope/decomposed-refs.js +2 -2
- package/dist/scope/project-definition-sync.js +3 -3
- package/dist/scope/vbrief-ref.d.ts +2 -1
- package/dist/scope/vbrief-ref.js +3 -2
- package/dist/spec-authority/constants.d.ts +6 -0
- package/dist/spec-authority/constants.js +11 -0
- package/dist/spec-authority/resolver.js +3 -3
- package/dist/swarm/finalize-cohort-cli.d.ts +5 -0
- package/dist/swarm/finalize-cohort-cli.js +100 -0
- package/dist/swarm/finalize-cohort.d.ts +39 -0
- package/dist/swarm/finalize-cohort.js +589 -0
- package/dist/swarm/index.d.ts +2 -0
- package/dist/swarm/index.js +2 -0
- package/dist/swarm/launch.d.ts +13 -0
- package/dist/swarm/launch.js +19 -7
- package/dist/swarm/routing-verify.d.ts +1 -1
- package/dist/swarm/routing-verify.js +11 -10
- package/dist/swarm/routing.d.ts +9 -0
- package/dist/swarm/routing.js +46 -0
- package/dist/swarm/verify-review-clean-cli.js +25 -1
- package/dist/swarm/verify-review-clean.d.ts +9 -1
- package/dist/swarm/verify-review-clean.js +76 -3
- package/dist/triage/queue/index.d.ts +1 -0
- package/dist/triage/queue/index.js +1 -0
- package/dist/triage/queue/project.d.ts +6 -1
- package/dist/triage/queue/project.js +9 -4
- package/dist/triage/queue/reconcile-live-state.d.ts +42 -0
- package/dist/triage/queue/reconcile-live-state.js +54 -0
- package/dist/triage/scope/mutations-core.js +91 -81
- package/dist/triage/scope/resolve.js +4 -3
- package/dist/triage/scope-drift/add-ignore.js +33 -32
- package/dist/triage/scope-drift/scope-rules.js +3 -3
- package/dist/triage/subscribe/index.js +51 -45
- package/dist/triage/welcome/writers.js +79 -64
- package/dist/vbrief-build/project-definition-io.js +8 -3
- package/dist/vbrief-validate/paths.js +8 -4
- package/dist/vbrief-validate/precutover.d.ts +1 -1
- package/dist/vbrief-validate/precutover.js +12 -10
- package/dist/verify-source/biome-config.d.ts +41 -0
- package/dist/verify-source/biome-config.js +97 -0
- package/dist/verify-source/index.d.ts +1 -0
- package/dist/verify-source/index.js +1 -0
- package/dist/xbrief-migrate/agents-header.d.ts +69 -0
- package/dist/xbrief-migrate/agents-header.js +179 -0
- package/dist/xbrief-migrate/fs-helpers.d.ts +3 -0
- package/dist/xbrief-migrate/fs-helpers.js +11 -0
- package/dist/xbrief-migrate/index.d.ts +1 -0
- package/dist/xbrief-migrate/index.js +1 -0
- package/dist/xbrief-migrate/migrate-project.js +29 -10
- package/package.json +15 -7
- package/dist/install-upgrade/index.d.ts +0 -13
- package/dist/install-upgrade/index.js +0 -251
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const FAILED_CONCLUSIONS = new Set(["failure", "cancelled", "timed_out"]);
|
|
2
|
+
const PENDING_STATUSES = new Set(["queued", "in_progress"]);
|
|
3
|
+
function isPending(status, conclusion) {
|
|
4
|
+
if (PENDING_STATUSES.has(status)) {
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
return status !== "completed" || conclusion === "none";
|
|
8
|
+
}
|
|
9
|
+
export function buildCiSummaryLine(summary) {
|
|
10
|
+
const passed = summary.checked_count - summary.failed_required.length - summary.pending_required.length;
|
|
11
|
+
return (`CI check-runs: ${passed} passed / ` +
|
|
12
|
+
`${summary.failed_required.length} failed / ${summary.pending_required.length} pending`);
|
|
13
|
+
}
|
|
14
|
+
export function evaluateCiGate(checkRuns, options = {}) {
|
|
15
|
+
if (options.skipCi === true) {
|
|
16
|
+
return {
|
|
17
|
+
failures: [],
|
|
18
|
+
summary: {
|
|
19
|
+
ready_state: "skipped",
|
|
20
|
+
checked_count: 0,
|
|
21
|
+
ignored_checks: [],
|
|
22
|
+
failed_required: [],
|
|
23
|
+
pending_required: [],
|
|
24
|
+
conclusions: [],
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const ignoredSet = new Set(options.ignoreCheckNames ?? []);
|
|
29
|
+
const ignoredChecks = [];
|
|
30
|
+
const failedRequired = [];
|
|
31
|
+
const pendingRequired = [];
|
|
32
|
+
const conclusions = [];
|
|
33
|
+
for (const run of checkRuns) {
|
|
34
|
+
const ignored = ignoredSet.has(run.name);
|
|
35
|
+
if (ignored) {
|
|
36
|
+
ignoredChecks.push(run.name);
|
|
37
|
+
}
|
|
38
|
+
const required = !ignored;
|
|
39
|
+
conclusions.push({
|
|
40
|
+
name: run.name,
|
|
41
|
+
status: run.status,
|
|
42
|
+
conclusion: run.conclusion,
|
|
43
|
+
required,
|
|
44
|
+
ignored,
|
|
45
|
+
});
|
|
46
|
+
if (!required) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (FAILED_CONCLUSIONS.has(run.conclusion)) {
|
|
50
|
+
failedRequired.push(`${run.name} (${run.conclusion})`);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (isPending(run.status, run.conclusion)) {
|
|
54
|
+
pendingRequired.push(`${run.name} (${run.status})`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const failures = [];
|
|
58
|
+
if (failedRequired.length > 0) {
|
|
59
|
+
failures.push(`Required CI check-runs failed: ${failedRequired.join(", ")}. ` +
|
|
60
|
+
"Required checks fail closed by default (#2169).");
|
|
61
|
+
}
|
|
62
|
+
if (pendingRequired.length > 0) {
|
|
63
|
+
failures.push(`Required CI check-runs still running (not-ready-yet): ${pendingRequired.join(", ")}. ` +
|
|
64
|
+
"Wait for required checks to finish before merge.");
|
|
65
|
+
}
|
|
66
|
+
const readyState = failedRequired.length > 0 ? "blocked" : pendingRequired.length > 0 ? "not_ready_yet" : "ready";
|
|
67
|
+
return {
|
|
68
|
+
failures,
|
|
69
|
+
summary: {
|
|
70
|
+
ready_state: readyState,
|
|
71
|
+
checked_count: conclusions.filter((item) => item.required).length,
|
|
72
|
+
ignored_checks: ignoredChecks,
|
|
73
|
+
failed_required: failedRequired,
|
|
74
|
+
pending_required: pendingRequired,
|
|
75
|
+
conclusions,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=ci-gate.js.map
|
|
@@ -1,7 +1,21 @@
|
|
|
1
|
+
import type { CiGateOptions } from "./ci-gate.js";
|
|
1
2
|
import { isMergeReady } from "./evaluate.js";
|
|
3
|
+
import { type MergeabilitySignal } from "./mergeability.js";
|
|
4
|
+
import type { SlizardGateOptions } from "./slizard-gate.js";
|
|
2
5
|
import type { GateResult, RunGhFn } from "./types.js";
|
|
3
6
|
declare function buildGateResult(prNumber: number, repo: string | null, headSha: string | null, body: string, via: string, partialData?: Record<string, unknown>, error?: string | null): GateResult;
|
|
7
|
+
/** Injectable GitHub-mergeability read seam (#2260) — keeps unit tests hermetic. */
|
|
8
|
+
export type FetchMergeabilityFn = (prNumber: number, repo: string, runGh: RunGhFn) => MergeabilitySignal;
|
|
9
|
+
export interface ComputeGateOptions extends CiGateOptions, SlizardGateOptions {
|
|
10
|
+
/** Override the GitHub-mergeability read (defaults to the REST reader). */
|
|
11
|
+
readonly fetchMergeabilityFn?: FetchMergeabilityFn;
|
|
12
|
+
/**
|
|
13
|
+
* Disable the #2260 mergeability reconciliation (defaults to enabled). When
|
|
14
|
+
* off, an absent/stale review verdict blocks even on a GitHub-CLEAN PR.
|
|
15
|
+
*/
|
|
16
|
+
readonly disableMergeabilityReconcile?: boolean;
|
|
17
|
+
}
|
|
4
18
|
/** Run the primary->fallback1->fallback2 cascade and return a result. */
|
|
5
|
-
export declare function computeGateResult(prNumber: number, repo: string | null, runGh: RunGhFn): GateResult;
|
|
19
|
+
export declare function computeGateResult(prNumber: number, repo: string | null, runGh: RunGhFn, options?: ComputeGateOptions): GateResult;
|
|
6
20
|
export { buildGateResult, isMergeReady };
|
|
7
21
|
//# sourceMappingURL=compute.d.ts.map
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { buildCiSummaryLine, evaluateCiGate } from "./ci-gate.js";
|
|
1
2
|
import { FALLBACK2_NOT_CLEAN_MSG, VIA_ERROR, VIA_FALLBACK1, VIA_FALLBACK2, VIA_PRIMARY, } from "./constants.js";
|
|
2
3
|
import { evaluateGates, isMergeReady } from "./evaluate.js";
|
|
3
4
|
import { fetchCheckRunsRest, fetchGreptileBodyRest, fetchGreptileCommentBody, fetchPrHeadSha, fetchPrHeadShaRest, resolveRepo, } from "./gh.js";
|
|
5
|
+
import { fetchMergeability, isGithubMergeableClean, mergeabilityToDict, verdictBlockIsSoftOnly, verdictShaIsStale, } from "./mergeability.js";
|
|
4
6
|
import { emptyVerdict, parseGreptileBody } from "./parse.js";
|
|
7
|
+
import { evaluateSlizardGate, isSlizardCheck } from "./slizard-gate.js";
|
|
5
8
|
function buildGateResult(prNumber, repo, headSha, body, via, partialData = {}, error = null) {
|
|
6
9
|
const verdict = parseGreptileBody(body);
|
|
7
10
|
const failures = evaluateGates(prNumber, headSha, verdict);
|
|
@@ -16,7 +19,123 @@ function buildGateResult(prNumber, repo, headSha, body, via, partialData = {}, e
|
|
|
16
19
|
error,
|
|
17
20
|
};
|
|
18
21
|
}
|
|
19
|
-
|
|
22
|
+
/** Run the CI + SLizard gates off a single check-runs fetch (#2169 / #2189). */
|
|
23
|
+
function evaluateCiAndSlizard(checkRuns, options) {
|
|
24
|
+
const slizardResult = evaluateSlizardGate(checkRuns, options);
|
|
25
|
+
// The SLizard check is scored by the structured gate, so exclude it from the
|
|
26
|
+
// generic CI required set to avoid double-counting the same run.
|
|
27
|
+
const slizardNames = checkRuns.filter((r) => isSlizardCheck(r.name)).map((r) => r.name);
|
|
28
|
+
const ciOptions = {
|
|
29
|
+
skipCi: options.skipCi,
|
|
30
|
+
ignoreCheckNames: [...(options.ignoreCheckNames ?? []), ...slizardNames],
|
|
31
|
+
};
|
|
32
|
+
const ciResult = evaluateCiGate(checkRuns, ciOptions);
|
|
33
|
+
return {
|
|
34
|
+
failures: [...ciResult.failures, ...slizardResult.failures],
|
|
35
|
+
ci: { ...ciResult.summary, summary_line: buildCiSummaryLine(ciResult.summary) },
|
|
36
|
+
slizard: { ...slizardResult.summary },
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function applyCiGateForHead(repo, headSha, runGh, options) {
|
|
40
|
+
if (repo === null) {
|
|
41
|
+
return {
|
|
42
|
+
failures: [
|
|
43
|
+
"Could not resolve repo for required CI check-run gate. " +
|
|
44
|
+
"Use --repo OWNER/REPO or run from a checked-out repository.",
|
|
45
|
+
],
|
|
46
|
+
ci: {
|
|
47
|
+
ready_state: "blocked",
|
|
48
|
+
error: "repo unresolved for check-runs lookup",
|
|
49
|
+
},
|
|
50
|
+
slizard: {
|
|
51
|
+
ready_state: "skipped",
|
|
52
|
+
present: false,
|
|
53
|
+
summary_line: "SLizard review: skipped (repo unresolved for check-runs lookup)",
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// When CI is skipped we do not fetch check-runs, so the SLizard gate cannot
|
|
58
|
+
// evaluate either; run both gates against an empty set to honor the overrides.
|
|
59
|
+
if (options.skipCi === true) {
|
|
60
|
+
const outcome = evaluateCiAndSlizard([], options);
|
|
61
|
+
outcome.ci.summary_line = "CI check-runs: skipped (--skip-ci)";
|
|
62
|
+
return { failures: outcome.failures, ci: outcome.ci, slizard: outcome.slizard };
|
|
63
|
+
}
|
|
64
|
+
const check = fetchCheckRunsRest(headSha, repo, runGh);
|
|
65
|
+
if (check.summary === null) {
|
|
66
|
+
return {
|
|
67
|
+
failures: [
|
|
68
|
+
"Required CI check-runs could not be fetched; fail closed by default (#2169). " +
|
|
69
|
+
`Root cause: ${check.error}`,
|
|
70
|
+
],
|
|
71
|
+
ci: {
|
|
72
|
+
ready_state: "blocked",
|
|
73
|
+
error: check.error,
|
|
74
|
+
checked_count: 0,
|
|
75
|
+
ignored_checks: [...(options.ignoreCheckNames ?? [])],
|
|
76
|
+
failed_required: [],
|
|
77
|
+
pending_required: [],
|
|
78
|
+
conclusions: [],
|
|
79
|
+
},
|
|
80
|
+
slizard: {
|
|
81
|
+
ready_state: "skipped",
|
|
82
|
+
present: false,
|
|
83
|
+
summary_line: `SLizard review: skipped (check-runs unavailable: ${check.error})`,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const outcome = evaluateCiAndSlizard(check.checkRuns, options);
|
|
88
|
+
return { failures: outcome.failures, ci: outcome.ci, slizard: outcome.slizard };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Score the verdict gate for a resolved head SHA, then (when the review verdict
|
|
92
|
+
* is only soft-blocked -- absent or stale for the current head) reconcile
|
|
93
|
+
* against GitHub's own mergeability (#2260). Shared by primary + fallback1.
|
|
94
|
+
*/
|
|
95
|
+
function finalizeVerdictGate(prNumber, repo, headSha, verdict, runGh, options) {
|
|
96
|
+
const failures = evaluateGates(prNumber, headSha, verdict);
|
|
97
|
+
const partialData = {};
|
|
98
|
+
const resolved = resolveRepo(repo, runGh);
|
|
99
|
+
if (failures.length === 0) {
|
|
100
|
+
const ci = applyCiGateForHead(resolved.repo, headSha, runGh, options);
|
|
101
|
+
failures.push(...ci.failures);
|
|
102
|
+
partialData.ci = ci.ci;
|
|
103
|
+
partialData.slizard = ci.slizard;
|
|
104
|
+
return { failures, partialData };
|
|
105
|
+
}
|
|
106
|
+
// #2260 reconciliation: the verdict gate failed. If the block is a HARD
|
|
107
|
+
// finding (genuine P0/P1, ERRORED, low confidence on the current head), keep
|
|
108
|
+
// blocking. Only reconcile a SOFT block (verdict absent / stale head SHA).
|
|
109
|
+
if (options.disableMergeabilityReconcile === true ||
|
|
110
|
+
resolved.repo === null ||
|
|
111
|
+
!verdictBlockIsSoftOnly(verdict, headSha)) {
|
|
112
|
+
return { failures, partialData };
|
|
113
|
+
}
|
|
114
|
+
const ci = applyCiGateForHead(resolved.repo, headSha, runGh, options);
|
|
115
|
+
partialData.ci = ci.ci;
|
|
116
|
+
partialData.slizard = ci.slizard;
|
|
117
|
+
const fetchMergeabilityFn = options.fetchMergeabilityFn ?? fetchMergeability;
|
|
118
|
+
const signal = fetchMergeabilityFn(prNumber, resolved.repo, runGh);
|
|
119
|
+
partialData.mergeability = mergeabilityToDict(signal);
|
|
120
|
+
if (isGithubMergeableClean(signal)) {
|
|
121
|
+
// GitHub itself reports CLEAN + MERGEABLE: required checks are green and the
|
|
122
|
+
// branch is mergeable. Proceed even though the OPTIONAL review verdict is
|
|
123
|
+
// absent/stale for the head SHA -- exactly the manual `gh pr merge --admin`
|
|
124
|
+
// case this fix reproduces automatically (#2260).
|
|
125
|
+
partialData.verdict_override = {
|
|
126
|
+
reason: verdictShaIsStale(verdict, headSha) ? "verdict-stale-head-sha" : "verdict-absent",
|
|
127
|
+
basis: "github mergeable_state=clean + mergeable=true (mergeStateStatus:CLEAN + " +
|
|
128
|
+
"mergeable:MERGEABLE); required checks green per GitHub branch protection (#2260)",
|
|
129
|
+
overridden_failures: [...failures],
|
|
130
|
+
};
|
|
131
|
+
return { failures: [], partialData };
|
|
132
|
+
}
|
|
133
|
+
// Not overridable (GitHub not CLEAN/MERGEABLE). Preserve the soft verdict
|
|
134
|
+
// failures and surface any CI failures so the heartbeat's blocked-on is clear.
|
|
135
|
+
failures.push(...ci.failures);
|
|
136
|
+
return { failures, partialData };
|
|
137
|
+
}
|
|
138
|
+
function computePrimary(prNumber, repo, runGh, options) {
|
|
20
139
|
const partial = {};
|
|
21
140
|
const headSha = fetchPrHeadSha(prNumber, repo, runGh);
|
|
22
141
|
if (headSha === null) {
|
|
@@ -30,7 +149,7 @@ function computePrimary(prNumber, repo, runGh) {
|
|
|
30
149
|
return { result: null, partial };
|
|
31
150
|
}
|
|
32
151
|
const verdict = parseGreptileBody(body);
|
|
33
|
-
const failures =
|
|
152
|
+
const { failures, partialData } = finalizeVerdictGate(prNumber, repo, headSha, verdict, runGh, options);
|
|
34
153
|
return {
|
|
35
154
|
result: {
|
|
36
155
|
prNumber,
|
|
@@ -39,13 +158,13 @@ function computePrimary(prNumber, repo, runGh) {
|
|
|
39
158
|
verdict,
|
|
40
159
|
failures,
|
|
41
160
|
via: VIA_PRIMARY,
|
|
42
|
-
partialData
|
|
161
|
+
partialData,
|
|
43
162
|
error: null,
|
|
44
163
|
},
|
|
45
164
|
partial,
|
|
46
165
|
};
|
|
47
166
|
}
|
|
48
|
-
function computeFallback1(prNumber, repo, primaryPartial, runGh) {
|
|
167
|
+
function computeFallback1(prNumber, repo, primaryPartial, runGh, options) {
|
|
49
168
|
const partial = { ...primaryPartial };
|
|
50
169
|
const resolved = resolveRepo(repo, runGh);
|
|
51
170
|
if (resolved.repo === null) {
|
|
@@ -68,20 +187,21 @@ function computeFallback1(prNumber, repo, primaryPartial, runGh) {
|
|
|
68
187
|
return { result: null, partial };
|
|
69
188
|
}
|
|
70
189
|
const verdict = parseGreptileBody(bodyResult.body);
|
|
71
|
-
const
|
|
190
|
+
const gate = finalizeVerdictGate(prNumber, resolved.repo, headSha, verdict, runGh, options);
|
|
72
191
|
const partialData = {};
|
|
73
192
|
for (const [key, value] of Object.entries(partial)) {
|
|
74
193
|
if (key !== "head_sha") {
|
|
75
194
|
partialData[key] = value;
|
|
76
195
|
}
|
|
77
196
|
}
|
|
197
|
+
Object.assign(partialData, gate.partialData);
|
|
78
198
|
return {
|
|
79
199
|
result: {
|
|
80
200
|
prNumber,
|
|
81
201
|
repo: resolved.repo,
|
|
82
202
|
headSha,
|
|
83
203
|
verdict,
|
|
84
|
-
failures,
|
|
204
|
+
failures: gate.failures,
|
|
85
205
|
via: VIA_FALLBACK1,
|
|
86
206
|
partialData,
|
|
87
207
|
error: null,
|
|
@@ -191,12 +311,12 @@ function errorResult(prNumber, repo, partial) {
|
|
|
191
311
|
};
|
|
192
312
|
}
|
|
193
313
|
/** Run the primary->fallback1->fallback2 cascade and return a result. */
|
|
194
|
-
export function computeGateResult(prNumber, repo, runGh) {
|
|
195
|
-
let { result, partial } = computePrimary(prNumber, repo, runGh);
|
|
314
|
+
export function computeGateResult(prNumber, repo, runGh, options = {}) {
|
|
315
|
+
let { result, partial } = computePrimary(prNumber, repo, runGh, options);
|
|
196
316
|
if (result !== null) {
|
|
197
317
|
return result;
|
|
198
318
|
}
|
|
199
|
-
({ result, partial } = computeFallback1(prNumber, repo, partial, runGh));
|
|
319
|
+
({ result, partial } = computeFallback1(prNumber, repo, partial, runGh, options));
|
|
200
320
|
if (result !== null) {
|
|
201
321
|
return result;
|
|
202
322
|
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import type { RunGhFn, RunGhResult } from "./types.js";
|
|
2
|
+
export interface CheckRunRecord {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly status: string;
|
|
5
|
+
readonly conclusion: string;
|
|
6
|
+
/** check-run `output.summary` text, when present (used by the SLizard verdict gate, #2189). */
|
|
7
|
+
readonly summary?: string;
|
|
8
|
+
}
|
|
2
9
|
/** UTF-8-safe gh capture via execFile (no shell) — mirrors _safe_subprocess.run_text (#1366). */
|
|
3
10
|
export declare function defaultRunGh(cmd: readonly string[]): RunGhResult;
|
|
4
11
|
export declare function fetchPrHeadSha(prNumber: number, repo: string | null, runGh: RunGhFn): string | null;
|
|
@@ -17,6 +24,7 @@ export declare function fetchPrHeadShaRest(prNumber: number, repo: string, runGh
|
|
|
17
24
|
};
|
|
18
25
|
export declare function fetchCheckRunsRest(sha: string, repo: string, runGh: RunGhFn): {
|
|
19
26
|
summary: Record<string, unknown> | null;
|
|
27
|
+
checkRuns: CheckRunRecord[];
|
|
20
28
|
error: string;
|
|
21
29
|
};
|
|
22
30
|
//# sourceMappingURL=gh.d.ts.map
|
|
@@ -229,16 +229,30 @@ export function fetchPrHeadShaRest(prNumber, repo, runGh) {
|
|
|
229
229
|
}
|
|
230
230
|
return { sha: null, error: "PR JSON missing head.sha" };
|
|
231
231
|
}
|
|
232
|
+
/** Pull the `output.summary` string off a raw check-run payload, if present (#2189). */
|
|
233
|
+
function extractCheckRunSummary(run) {
|
|
234
|
+
const output = run.output;
|
|
235
|
+
if (output === null || typeof output !== "object" || Array.isArray(output)) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
const summary = output.summary;
|
|
239
|
+
return typeof summary === "string" && summary.length > 0 ? summary : null;
|
|
240
|
+
}
|
|
232
241
|
export function fetchCheckRunsRest(sha, repo, runGh) {
|
|
233
242
|
const rc = runGh(["gh", "api", `repos/${repo}/commits/${sha}/check-runs`]);
|
|
234
243
|
if (rc.returncode !== 0) {
|
|
235
244
|
return {
|
|
236
245
|
summary: null,
|
|
246
|
+
checkRuns: [],
|
|
237
247
|
error: `gh api /commits/${"<"}sha>/check-runs failed: ${rc.stderr.trim()}`,
|
|
238
248
|
};
|
|
239
249
|
}
|
|
240
250
|
if (!rc.stdout.trim()) {
|
|
241
|
-
return {
|
|
251
|
+
return {
|
|
252
|
+
summary: null,
|
|
253
|
+
checkRuns: [],
|
|
254
|
+
error: "empty body from gh api /commits/<sha>/check-runs",
|
|
255
|
+
};
|
|
242
256
|
}
|
|
243
257
|
let payload;
|
|
244
258
|
try {
|
|
@@ -246,20 +260,22 @@ export function fetchCheckRunsRest(sha, repo, runGh) {
|
|
|
246
260
|
}
|
|
247
261
|
catch (exc) {
|
|
248
262
|
const message = exc instanceof Error ? exc.message : String(exc);
|
|
249
|
-
return { summary: null, error: `could not parse check-runs JSON: ${message}` };
|
|
263
|
+
return { summary: null, checkRuns: [], error: `could not parse check-runs JSON: ${message}` };
|
|
250
264
|
}
|
|
251
265
|
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
|
|
252
|
-
return { summary: null, error: "unexpected check-runs JSON shape (not a dict)" };
|
|
266
|
+
return { summary: null, checkRuns: [], error: "unexpected check-runs JSON shape (not a dict)" };
|
|
253
267
|
}
|
|
254
268
|
const runs = payload.check_runs;
|
|
255
269
|
if (!Array.isArray(runs)) {
|
|
256
|
-
return { summary: null, error: "check-runs JSON missing check_runs list" };
|
|
270
|
+
return { summary: null, checkRuns: [], error: "check-runs JSON missing check_runs list" };
|
|
257
271
|
}
|
|
272
|
+
const checkRuns = [];
|
|
258
273
|
const summary = {
|
|
259
274
|
total: runs.length,
|
|
260
275
|
by_status: {},
|
|
261
276
|
by_conclusion: {},
|
|
262
277
|
greptile_review: null,
|
|
278
|
+
slizard_review: null,
|
|
263
279
|
};
|
|
264
280
|
const byStatus = summary.by_status;
|
|
265
281
|
const byConclusion = summary.by_conclusion;
|
|
@@ -270,12 +286,21 @@ export function fetchCheckRunsRest(sha, repo, runGh) {
|
|
|
270
286
|
const r = run;
|
|
271
287
|
const status = typeof r.status === "string" ? r.status : "unknown";
|
|
272
288
|
const conclusion = typeof r.conclusion === "string" ? r.conclusion : "none";
|
|
289
|
+
const name = typeof r.name === "string" && r.name.length > 0 ? r.name : "<unnamed>";
|
|
290
|
+
const runSummary = extractCheckRunSummary(r);
|
|
291
|
+
const record = runSummary === null
|
|
292
|
+
? { name, status, conclusion }
|
|
293
|
+
: { name, status, conclusion, summary: runSummary };
|
|
294
|
+
checkRuns.push(record);
|
|
273
295
|
byStatus[status] = (byStatus[status] ?? 0) + 1;
|
|
274
296
|
byConclusion[conclusion] = (byConclusion[conclusion] ?? 0) + 1;
|
|
275
297
|
if (r.name === "Greptile Review") {
|
|
276
298
|
summary.greptile_review = { status, conclusion };
|
|
277
299
|
}
|
|
300
|
+
if (name.toLowerCase().includes("slizard")) {
|
|
301
|
+
summary.slizard_review = { status, conclusion };
|
|
302
|
+
}
|
|
278
303
|
}
|
|
279
|
-
return { summary, error: "" };
|
|
304
|
+
return { summary, checkRuns, error: "" };
|
|
280
305
|
}
|
|
281
306
|
//# sourceMappingURL=gh.js.map
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { buildCiSummaryLine, evaluateCiGate } from "./ci-gate.js";
|
|
2
|
+
export { type ComputeGateOptions, computeGateResult, type FetchMergeabilityFn } from "./compute.js";
|
|
2
3
|
export * from "./constants.js";
|
|
3
4
|
export { evaluateGates, isMergeReady } from "./evaluate.js";
|
|
4
5
|
export { defaultRunGh } from "./gh.js";
|
|
5
6
|
export { cmdPrMergeReadiness, parseArgs, run } from "./main.js";
|
|
7
|
+
export { fetchMergeability, isGithubMergeableClean, MERGE_STATE_CLEAN, type MergeabilitySignal, mergeabilityToDict, verdictBlockIsSoftOnly, verdictShaIsStale, } from "./mergeability.js";
|
|
6
8
|
export { emitJson, exitCodeFor, gateResultToDict, printHuman } from "./output.js";
|
|
7
9
|
export { emptyVerdict, isInformalCleanMissingCanonicalFields, parseGreptileBody } from "./parse.js";
|
|
10
|
+
export type { SlizardGateOptions, SlizardGateResult, SlizardGateSummary, SlizardVerdict, } from "./slizard-gate.js";
|
|
11
|
+
export { evaluateSlizardGate, isSlizardCheck, parseSlizardVerdict, SLIZARD_CHECK_NAME, } from "./slizard-gate.js";
|
|
8
12
|
export type { GateResult, GreptileVerdict, RunGhFn, RunGhResult } from "./types.js";
|
|
9
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
export { buildCiSummaryLine, evaluateCiGate } from "./ci-gate.js";
|
|
1
2
|
export { computeGateResult } from "./compute.js";
|
|
2
3
|
export * from "./constants.js";
|
|
3
4
|
export { evaluateGates, isMergeReady } from "./evaluate.js";
|
|
4
5
|
export { defaultRunGh } from "./gh.js";
|
|
5
6
|
export { cmdPrMergeReadiness, parseArgs, run } from "./main.js";
|
|
7
|
+
export { fetchMergeability, isGithubMergeableClean, MERGE_STATE_CLEAN, mergeabilityToDict, verdictBlockIsSoftOnly, verdictShaIsStale, } from "./mergeability.js";
|
|
6
8
|
export { emitJson, exitCodeFor, gateResultToDict, printHuman } from "./output.js";
|
|
7
9
|
export { emptyVerdict, isInformalCleanMissingCanonicalFields, parseGreptileBody } from "./parse.js";
|
|
10
|
+
export { evaluateSlizardGate, isSlizardCheck, parseSlizardVerdict, SLIZARD_CHECK_NAME, } from "./slizard-gate.js";
|
|
8
11
|
//# sourceMappingURL=index.js.map
|
|
@@ -3,6 +3,9 @@ export interface ParsedArgs {
|
|
|
3
3
|
readonly prNumber: number | null;
|
|
4
4
|
readonly repo: string | null;
|
|
5
5
|
readonly emitJson: boolean;
|
|
6
|
+
readonly skipCi: boolean;
|
|
7
|
+
readonly skipSlizard: boolean;
|
|
8
|
+
readonly ciIgnoreChecks: readonly string[];
|
|
6
9
|
readonly error?: string;
|
|
7
10
|
}
|
|
8
11
|
export declare function parseArgs(argv: readonly string[]): ParsedArgs;
|
|
@@ -5,15 +5,51 @@ export function parseArgs(argv) {
|
|
|
5
5
|
let prNumber = null;
|
|
6
6
|
let repo = null;
|
|
7
7
|
let json = false;
|
|
8
|
+
let skipCi = false;
|
|
9
|
+
let skipSlizard = false;
|
|
10
|
+
const ciIgnoreChecks = [];
|
|
8
11
|
for (let i = 0; i < argv.length; i += 1) {
|
|
9
12
|
const arg = argv[i];
|
|
10
13
|
if (arg === "--json") {
|
|
11
14
|
json = true;
|
|
12
15
|
}
|
|
16
|
+
else if (arg === "--skip-ci") {
|
|
17
|
+
skipCi = true;
|
|
18
|
+
}
|
|
19
|
+
else if (arg === "--skip-slizard") {
|
|
20
|
+
skipSlizard = true;
|
|
21
|
+
}
|
|
22
|
+
else if (arg === "--ci-ignore-check") {
|
|
23
|
+
const value = argv[i + 1];
|
|
24
|
+
if (value === undefined) {
|
|
25
|
+
return {
|
|
26
|
+
prNumber,
|
|
27
|
+
repo,
|
|
28
|
+
emitJson: json,
|
|
29
|
+
skipCi,
|
|
30
|
+
skipSlizard,
|
|
31
|
+
ciIgnoreChecks,
|
|
32
|
+
error: "argument --ci-ignore-check: expected one argument",
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
ciIgnoreChecks.push(value);
|
|
36
|
+
i += 1;
|
|
37
|
+
}
|
|
38
|
+
else if (arg?.startsWith("--ci-ignore-check=")) {
|
|
39
|
+
ciIgnoreChecks.push(arg.slice("--ci-ignore-check=".length));
|
|
40
|
+
}
|
|
13
41
|
else if (arg === "--repo") {
|
|
14
42
|
const value = argv[i + 1];
|
|
15
43
|
if (value === undefined) {
|
|
16
|
-
return {
|
|
44
|
+
return {
|
|
45
|
+
prNumber,
|
|
46
|
+
repo,
|
|
47
|
+
emitJson: json,
|
|
48
|
+
skipCi,
|
|
49
|
+
skipSlizard,
|
|
50
|
+
ciIgnoreChecks,
|
|
51
|
+
error: "argument --repo: expected one argument",
|
|
52
|
+
};
|
|
17
53
|
}
|
|
18
54
|
repo = value;
|
|
19
55
|
i += 1;
|
|
@@ -22,17 +58,41 @@ export function parseArgs(argv) {
|
|
|
22
58
|
repo = arg.slice("--repo=".length);
|
|
23
59
|
}
|
|
24
60
|
else if (arg?.startsWith("-")) {
|
|
25
|
-
return {
|
|
61
|
+
return {
|
|
62
|
+
prNumber,
|
|
63
|
+
repo,
|
|
64
|
+
emitJson: json,
|
|
65
|
+
skipCi,
|
|
66
|
+
skipSlizard,
|
|
67
|
+
ciIgnoreChecks,
|
|
68
|
+
error: `unrecognized arguments: ${arg}`,
|
|
69
|
+
};
|
|
26
70
|
}
|
|
27
71
|
else if (prNumber === null) {
|
|
28
72
|
const n = Number(arg);
|
|
29
73
|
if (!Number.isInteger(n) || n <= 0) {
|
|
30
|
-
return {
|
|
74
|
+
return {
|
|
75
|
+
prNumber,
|
|
76
|
+
repo,
|
|
77
|
+
emitJson: json,
|
|
78
|
+
skipCi,
|
|
79
|
+
skipSlizard,
|
|
80
|
+
ciIgnoreChecks,
|
|
81
|
+
error: `invalid PR number: ${arg}`,
|
|
82
|
+
};
|
|
31
83
|
}
|
|
32
84
|
prNumber = n;
|
|
33
85
|
}
|
|
34
86
|
else {
|
|
35
|
-
return {
|
|
87
|
+
return {
|
|
88
|
+
prNumber,
|
|
89
|
+
repo,
|
|
90
|
+
emitJson: json,
|
|
91
|
+
skipCi,
|
|
92
|
+
skipSlizard,
|
|
93
|
+
ciIgnoreChecks,
|
|
94
|
+
error: `unrecognized arguments: ${arg}`,
|
|
95
|
+
};
|
|
36
96
|
}
|
|
37
97
|
}
|
|
38
98
|
if (prNumber === null) {
|
|
@@ -40,10 +100,13 @@ export function parseArgs(argv) {
|
|
|
40
100
|
prNumber,
|
|
41
101
|
repo,
|
|
42
102
|
emitJson: json,
|
|
103
|
+
skipCi,
|
|
104
|
+
skipSlizard,
|
|
105
|
+
ciIgnoreChecks,
|
|
43
106
|
error: "the following arguments are required: pr_number",
|
|
44
107
|
};
|
|
45
108
|
}
|
|
46
|
-
return { prNumber, repo, emitJson: json };
|
|
109
|
+
return { prNumber, repo, emitJson: json, skipCi, skipSlizard, ciIgnoreChecks };
|
|
47
110
|
}
|
|
48
111
|
export function run(argv, options = {}) {
|
|
49
112
|
const args = parseArgs(argv);
|
|
@@ -52,7 +115,11 @@ export function run(argv, options = {}) {
|
|
|
52
115
|
return 2;
|
|
53
116
|
}
|
|
54
117
|
const runGh = options.runGh ?? defaultRunGh;
|
|
55
|
-
const result = computeGateResult(args.prNumber, args.repo, runGh
|
|
118
|
+
const result = computeGateResult(args.prNumber, args.repo, runGh, {
|
|
119
|
+
skipCi: args.skipCi,
|
|
120
|
+
skipSlizard: args.skipSlizard,
|
|
121
|
+
ignoreCheckNames: args.ciIgnoreChecks,
|
|
122
|
+
});
|
|
56
123
|
if (args.emitJson) {
|
|
57
124
|
process.stdout.write(emitJson(result));
|
|
58
125
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { GreptileVerdict, RunGhFn } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* GitHub's authoritative mergeability signal, read over REST (#2260).
|
|
4
|
+
*
|
|
5
|
+
* REST `mergeable_state` maps 1:1 to GraphQL `mergeStateStatus`
|
|
6
|
+
* (e.g. `"clean"` == `CLEAN`); `mergeable` (boolean|null) maps to
|
|
7
|
+
* `mergeable` (`MERGEABLE`/`CONFLICTING`/`UNKNOWN`). We read REST to honor
|
|
8
|
+
* the "prefer REST over GraphQL" rule (#954) and to reuse the same
|
|
9
|
+
* `gh api repos/<owner>/<repo>/pulls/<N>` surface fallback2 already uses.
|
|
10
|
+
*/
|
|
11
|
+
export interface MergeabilitySignal {
|
|
12
|
+
readonly mergeableState: string | null;
|
|
13
|
+
readonly mergeable: boolean | null;
|
|
14
|
+
readonly error: string | null;
|
|
15
|
+
}
|
|
16
|
+
/** REST `mergeable_state` value that mirrors GraphQL `mergeStateStatus: CLEAN`. */
|
|
17
|
+
export declare const MERGE_STATE_CLEAN = "clean";
|
|
18
|
+
/**
|
|
19
|
+
* Read GitHub's own mergeability verdict for a PR over REST.
|
|
20
|
+
*
|
|
21
|
+
* Injectable via `runGh` so unit tests stay hermetic (no live network).
|
|
22
|
+
*/
|
|
23
|
+
export declare function fetchMergeability(prNumber: number, repo: string, runGh: RunGhFn): MergeabilitySignal;
|
|
24
|
+
/** True when GitHub itself reports the PR as CLEAN + MERGEABLE (#2260). */
|
|
25
|
+
export declare function isGithubMergeableClean(signal: MergeabilitySignal): boolean;
|
|
26
|
+
/** Serialise the mergeability signal into the partial_data envelope shape. */
|
|
27
|
+
export declare function mergeabilityToDict(signal: MergeabilitySignal): Record<string, unknown>;
|
|
28
|
+
/** True when a parsed verdict SHA does not match the current PR head SHA. */
|
|
29
|
+
export declare function verdictShaIsStale(verdict: GreptileVerdict, headSha: string | null): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Classify whether the verdict-based merge block is ONLY "soft" (#2260).
|
|
32
|
+
*
|
|
33
|
+
* A soft block means the review verdict is absent or pinned to a prior head
|
|
34
|
+
* SHA (rebased staleness) -- i.e. the review has not spoken about the CURRENT
|
|
35
|
+
* head. It is safe to reconcile a soft block against GitHub mergeability.
|
|
36
|
+
*
|
|
37
|
+
* A HARD block -- a genuine P0/P1 finding, an ERRORED review, or a low
|
|
38
|
+
* confidence score on the current head -- is NEVER soft; those must keep
|
|
39
|
+
* blocking regardless of GitHub mergeability (guardrail: do not merge a PR
|
|
40
|
+
* with a real P0/P1 review finding).
|
|
41
|
+
*/
|
|
42
|
+
export declare function verdictBlockIsSoftOnly(verdict: GreptileVerdict, headSha: string | null): boolean;
|
|
43
|
+
//# sourceMappingURL=mergeability.d.ts.map
|