@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.
Files changed (147) hide show
  1. package/dist/agents-md-advisory/evaluate.d.ts +44 -0
  2. package/dist/agents-md-advisory/evaluate.js +115 -0
  3. package/dist/agents-md-advisory/index.d.ts +2 -0
  4. package/dist/agents-md-advisory/index.js +2 -0
  5. package/dist/agents-md-budget/evaluate.d.ts +38 -0
  6. package/dist/agents-md-budget/evaluate.js +152 -0
  7. package/dist/agents-md-budget/index.d.ts +2 -0
  8. package/dist/agents-md-budget/index.js +2 -0
  9. package/dist/check/index.d.ts +1 -1
  10. package/dist/check/index.js +1 -1
  11. package/dist/check/orchestrator.d.ts +14 -4
  12. package/dist/check/orchestrator.js +30 -6
  13. package/dist/codebase/map.d.ts +1 -0
  14. package/dist/codebase/map.js +5 -2
  15. package/dist/doctor/checks.js +11 -11
  16. package/dist/doctor/constants.d.ts +7 -2
  17. package/dist/doctor/constants.js +17 -1
  18. package/dist/doctor/flags.js +5 -1
  19. package/dist/doctor/main.js +99 -8
  20. package/dist/doctor/payload-staleness.js +15 -6
  21. package/dist/doctor/types.d.ts +7 -0
  22. package/dist/forward-coverage/evaluate.d.ts +55 -0
  23. package/dist/forward-coverage/evaluate.js +254 -0
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +1 -0
  26. package/dist/init-deposit/constants.d.ts +14 -0
  27. package/dist/init-deposit/constants.js +10 -0
  28. package/dist/init-deposit/hygiene.d.ts +50 -0
  29. package/dist/init-deposit/hygiene.js +176 -0
  30. package/dist/init-deposit/init-deposit.d.ts +1 -0
  31. package/dist/init-deposit/init-deposit.js +7 -1
  32. package/dist/init-deposit/refresh.d.ts +2 -0
  33. package/dist/init-deposit/refresh.js +48 -30
  34. package/dist/init-deposit/scaffold.d.ts +3 -4
  35. package/dist/init-deposit/scaffold.js +3 -24
  36. package/dist/intake/github-auth-modes.d.ts +1 -0
  37. package/dist/intake/github-auth-modes.js +1 -0
  38. package/dist/intake/issue-ingest.d.ts +6 -2
  39. package/dist/intake/issue-ingest.js +65 -9
  40. package/dist/integration-e2e/helpers.d.ts +0 -1
  41. package/dist/integration-e2e/helpers.js +3 -7
  42. package/dist/layout/lifecycle-ref.d.ts +15 -0
  43. package/dist/layout/lifecycle-ref.js +55 -0
  44. package/dist/platform/agents-consumer-header.d.ts +13 -0
  45. package/dist/platform/agents-consumer-header.js +57 -0
  46. package/dist/platform/agents-md.d.ts +28 -0
  47. package/dist/platform/agents-md.js +38 -1
  48. package/dist/platform/index.d.ts +1 -0
  49. package/dist/platform/index.js +1 -0
  50. package/dist/policy/agents-md-advisory.d.ts +52 -0
  51. package/dist/policy/agents-md-advisory.js +63 -0
  52. package/dist/policy/agents-md-budget.d.ts +24 -0
  53. package/dist/policy/agents-md-budget.js +89 -0
  54. package/dist/policy/index.d.ts +1 -0
  55. package/dist/policy/index.js +1 -0
  56. package/dist/policy/resolve.js +56 -46
  57. package/dist/pr-merge-readiness/ci-gate.d.ts +28 -0
  58. package/dist/pr-merge-readiness/ci-gate.js +79 -0
  59. package/dist/pr-merge-readiness/compute.d.ts +15 -1
  60. package/dist/pr-merge-readiness/compute.js +129 -9
  61. package/dist/pr-merge-readiness/gh.d.ts +8 -0
  62. package/dist/pr-merge-readiness/gh.js +30 -5
  63. package/dist/pr-merge-readiness/index.d.ts +5 -1
  64. package/dist/pr-merge-readiness/index.js +3 -0
  65. package/dist/pr-merge-readiness/main.d.ts +3 -0
  66. package/dist/pr-merge-readiness/main.js +73 -6
  67. package/dist/pr-merge-readiness/mergeability.d.ts +43 -0
  68. package/dist/pr-merge-readiness/mergeability.js +105 -0
  69. package/dist/pr-merge-readiness/output.js +20 -0
  70. package/dist/pr-merge-readiness/slizard-gate.d.ts +47 -0
  71. package/dist/pr-merge-readiness/slizard-gate.js +119 -0
  72. package/dist/pr-monitor/index.d.ts +1 -1
  73. package/dist/pr-monitor/index.js +1 -1
  74. package/dist/pr-monitor/monitor.d.ts +8 -2
  75. package/dist/pr-monitor/monitor.js +35 -6
  76. package/dist/pr-wait-mergeable/wrappers.d.ts +12 -1
  77. package/dist/pr-wait-mergeable/wrappers.js +13 -5
  78. package/dist/pr-watch/constants.d.ts +33 -0
  79. package/dist/pr-watch/constants.js +33 -0
  80. package/dist/pr-watch/index.d.ts +6 -0
  81. package/dist/pr-watch/index.js +6 -0
  82. package/dist/pr-watch/main.d.ts +21 -0
  83. package/dist/pr-watch/main.js +206 -0
  84. package/dist/pr-watch/probe.d.ts +13 -0
  85. package/dist/pr-watch/probe.js +100 -0
  86. package/dist/pr-watch/types.d.ts +47 -0
  87. package/dist/pr-watch/types.js +2 -0
  88. package/dist/pr-watch/watch.d.ts +12 -0
  89. package/dist/pr-watch/watch.js +122 -0
  90. package/dist/preflight-cache/evaluate.d.ts +1 -0
  91. package/dist/preflight-cache/evaluate.js +17 -10
  92. package/dist/render/project-render.d.ts +16 -2
  93. package/dist/render/project-render.js +155 -97
  94. package/dist/scope/capacity-stamp.d.ts +24 -2
  95. package/dist/scope/capacity-stamp.js +80 -28
  96. package/dist/scope/decomposed-refs.js +2 -2
  97. package/dist/scope/project-definition-sync.js +3 -3
  98. package/dist/scope/vbrief-ref.d.ts +2 -1
  99. package/dist/scope/vbrief-ref.js +3 -2
  100. package/dist/spec-authority/constants.d.ts +6 -0
  101. package/dist/spec-authority/constants.js +11 -0
  102. package/dist/spec-authority/resolver.js +3 -3
  103. package/dist/swarm/finalize-cohort-cli.d.ts +5 -0
  104. package/dist/swarm/finalize-cohort-cli.js +100 -0
  105. package/dist/swarm/finalize-cohort.d.ts +39 -0
  106. package/dist/swarm/finalize-cohort.js +589 -0
  107. package/dist/swarm/index.d.ts +2 -0
  108. package/dist/swarm/index.js +2 -0
  109. package/dist/swarm/launch.d.ts +13 -0
  110. package/dist/swarm/launch.js +19 -7
  111. package/dist/swarm/routing-verify.d.ts +1 -1
  112. package/dist/swarm/routing-verify.js +11 -10
  113. package/dist/swarm/routing.d.ts +9 -0
  114. package/dist/swarm/routing.js +46 -0
  115. package/dist/swarm/verify-review-clean-cli.js +25 -1
  116. package/dist/swarm/verify-review-clean.d.ts +9 -1
  117. package/dist/swarm/verify-review-clean.js +76 -3
  118. package/dist/triage/queue/index.d.ts +1 -0
  119. package/dist/triage/queue/index.js +1 -0
  120. package/dist/triage/queue/project.d.ts +6 -1
  121. package/dist/triage/queue/project.js +9 -4
  122. package/dist/triage/queue/reconcile-live-state.d.ts +42 -0
  123. package/dist/triage/queue/reconcile-live-state.js +54 -0
  124. package/dist/triage/scope/mutations-core.js +91 -81
  125. package/dist/triage/scope/resolve.js +4 -3
  126. package/dist/triage/scope-drift/add-ignore.js +33 -32
  127. package/dist/triage/scope-drift/scope-rules.js +3 -3
  128. package/dist/triage/subscribe/index.js +51 -45
  129. package/dist/triage/welcome/writers.js +79 -64
  130. package/dist/vbrief-build/project-definition-io.js +8 -3
  131. package/dist/vbrief-validate/paths.js +8 -4
  132. package/dist/vbrief-validate/precutover.d.ts +1 -1
  133. package/dist/vbrief-validate/precutover.js +12 -10
  134. package/dist/verify-source/biome-config.d.ts +41 -0
  135. package/dist/verify-source/biome-config.js +97 -0
  136. package/dist/verify-source/index.d.ts +1 -0
  137. package/dist/verify-source/index.js +1 -0
  138. package/dist/xbrief-migrate/agents-header.d.ts +69 -0
  139. package/dist/xbrief-migrate/agents-header.js +179 -0
  140. package/dist/xbrief-migrate/fs-helpers.d.ts +3 -0
  141. package/dist/xbrief-migrate/fs-helpers.js +11 -0
  142. package/dist/xbrief-migrate/index.d.ts +1 -0
  143. package/dist/xbrief-migrate/index.js +1 -0
  144. package/dist/xbrief-migrate/migrate-project.js +29 -10
  145. package/package.json +15 -7
  146. package/dist/install-upgrade/index.d.ts +0 -13
  147. 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
- function computePrimary(prNumber, repo, runGh) {
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 = evaluateGates(prNumber, headSha, verdict);
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 failures = evaluateGates(prNumber, headSha, verdict);
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 { summary: null, error: "empty body from gh api /commits/<sha>/check-runs" };
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 { computeGateResult } from "./compute.js";
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 { prNumber, repo, emitJson: json, error: "argument --repo: expected one argument" };
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 { prNumber, repo, emitJson: json, error: `unrecognized arguments: ${arg}` };
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 { prNumber, repo, emitJson: json, error: `invalid PR number: ${arg}` };
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 { prNumber, repo, emitJson: json, error: `unrecognized arguments: ${arg}` };
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