willfire 0.1.36 → 0.1.38

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.
@@ -11,9 +11,8 @@ export function parseArgs(argv) {
11
11
  console.error(USAGE);
12
12
  process.exit(2);
13
13
  }
14
- // An unrecognised action is refused rather than ignored. Silently falling
15
- // back to the guess would turn a typo into a wrong prediction, which is the
16
- // failure this flag exists to remove.
14
+ // Refused, not ignored: falling back to the guess would turn a typo into a
15
+ // wrong prediction, the failure this flag exists to remove.
17
16
  const action = get("--action");
18
17
  if (action !== undefined && !isPrEventAction(action)) {
19
18
  console.error(`unknown --action: ${action}`);
package/dist/cli.js CHANGED
@@ -1,9 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  // Usage: pnpm predict --repo owner/name --pr N [--json]
3
- //
4
- // Auth: GH_TOKEN or GITHUB_TOKEN env var (any token with
5
- // contents/actions/pull-requests read). Inside an action, pass the workflow's
6
- // GITHUB_TOKEN.
3
+ // Auth: GH_TOKEN or GITHUB_TOKEN, needing contents/actions/pull-requests read.
7
4
  import { parseArgs } from "./cli/parseArgs.js";
8
5
  import { isWorkflowEntry } from "./entries/isWorkflowEntry.js";
9
6
  import { makeGithubClient } from "./predict/makeGithubClient.js";
@@ -33,8 +30,8 @@ if (isMain) {
33
30
  }
34
31
  }
35
32
  }
36
- // Last, and on the skip path too, so a red gate's first question which
37
- // commits was this read from? — is answered wherever the reader lands.
33
+ // Last, and on the skip path too, so a red gate's reader always sees which
34
+ // commits this was read from.
38
35
  for (const s of sources) {
39
36
  console.log(`# read ${s.owner}/${s.repo}@${s.ref} -> ${s.sha}`);
40
37
  }
@@ -1,11 +1,7 @@
1
1
  import { type Val } from "./val.js";
2
2
  /**
3
- * The functions worth modelling.
4
- *
5
- * `always()` is true by definition. `contains` on two known strings is the one
6
- * that unlocks the fleet's `gates` pattern. `fromJSON` is what a dynamic matrix
7
- * axis is built out of. `success()`, `failure()` and `cancelled()` depend on
8
- * jobs that have not run, and everything else is simply not modelled — all
9
- * unknown.
3
+ * `always()` is true by definition and `fromJSON` is what a dynamic matrix axis
4
+ * is built out of. The job-status functions depend on jobs that have not run,
5
+ * so they are unknown as is every function not modelled here.
10
6
  */
11
7
  export declare function applyFunction(name: string, args: Val[]): Val;
@@ -2,13 +2,9 @@ import { asBool } from "./asBool.js";
2
2
  import { fromJson } from "./fromJson.js";
3
3
  import { UNKNOWN } from "./val.js";
4
4
  /**
5
- * The functions worth modelling.
6
- *
7
- * `always()` is true by definition. `contains` on two known strings is the one
8
- * that unlocks the fleet's `gates` pattern. `fromJSON` is what a dynamic matrix
9
- * axis is built out of. `success()`, `failure()` and `cancelled()` depend on
10
- * jobs that have not run, and everything else is simply not modelled — all
11
- * unknown.
5
+ * `always()` is true by definition and `fromJSON` is what a dynamic matrix axis
6
+ * is built out of. The job-status functions depend on jobs that have not run,
7
+ * so they are unknown as is every function not modelled here.
12
8
  */
13
9
  export function applyFunction(name, args) {
14
10
  if (name === "always") {
@@ -1,10 +1,7 @@
1
1
  import { type Val } from "./val.js";
2
2
  /**
3
- * Wrap a decided answer as a concrete boolean value.
4
- *
5
- * Comparison, negation and the string functions all yield a real boolean, so
6
- * they produce a `value` — which keeps `!x == true` comparable. Only
7
- * short-circuiting produces the bare `truthy`/`falsy` points, because that is
8
- * the one case where truthiness is known and the value is not.
3
+ * A `value`, not a bare `truthy`/`falsy`, so `!x == true` stays comparable.
4
+ * Short-circuiting is the only producer of those points: it is the one case
5
+ * where truthiness is known and the value is not.
9
6
  */
10
7
  export declare function asBool(b: boolean | null): Val;
@@ -1,11 +1,8 @@
1
1
  import { UNKNOWN } from "./val.js";
2
2
  /**
3
- * Wrap a decided answer as a concrete boolean value.
4
- *
5
- * Comparison, negation and the string functions all yield a real boolean, so
6
- * they produce a `value` — which keeps `!x == true` comparable. Only
7
- * short-circuiting produces the bare `truthy`/`falsy` points, because that is
8
- * the one case where truthiness is known and the value is not.
3
+ * A `value`, not a bare `truthy`/`falsy`, so `!x == true` stays comparable.
4
+ * Short-circuiting is the only producer of those points: it is the one case
5
+ * where truthiness is known and the value is not.
9
6
  */
10
7
  export function asBool(b) {
11
8
  return b === null ? UNKNOWN : { kind: "value", v: b };
@@ -1,7 +1,6 @@
1
1
  import { type Val } from "./val.js";
2
2
  /**
3
- * `A && B`. Short-circuits from either side: a falsy left decides it, and so
4
- * does a falsy right, because a truthy left would then yield the falsy right.
5
- * Only "left unknown, right not falsy" is genuinely undecided.
3
+ * Short-circuits from either side: a falsy right decides it too, because a
4
+ * truthy left would then yield that falsy right.
6
5
  */
7
6
  export declare function coalesceAnd(left: Val, right: Val): Val;
@@ -1,9 +1,8 @@
1
1
  import { truthy } from "./truthy.js";
2
2
  import { UNKNOWN } from "./val.js";
3
3
  /**
4
- * `A && B`. Short-circuits from either side: a falsy left decides it, and so
5
- * does a falsy right, because a truthy left would then yield the falsy right.
6
- * Only "left unknown, right not falsy" is genuinely undecided.
4
+ * Short-circuits from either side: a falsy right decides it too, because a
5
+ * truthy left would then yield that falsy right.
7
6
  */
8
7
  export function coalesceAnd(left, right) {
9
8
  const l = truthy(left);
@@ -1,6 +1,6 @@
1
1
  import { type Val } from "./val.js";
2
2
  /**
3
- * `A || B`. The mirror image: a truthy left decides it, and so does a truthy
4
- * right, since a falsy left would then yield the truthy right.
3
+ * Short-circuits from either side: a truthy right decides it too, since a falsy
4
+ * left would then yield that truthy right.
5
5
  */
6
6
  export declare function coalesceOr(left: Val, right: Val): Val;
@@ -1,8 +1,8 @@
1
1
  import { truthy } from "./truthy.js";
2
2
  import { UNKNOWN } from "./val.js";
3
3
  /**
4
- * `A || B`. The mirror image: a truthy left decides it, and so does a truthy
5
- * right, since a falsy left would then yield the truthy right.
4
+ * Short-circuits from either side: a truthy right decides it too, since a falsy
5
+ * left would then yield that truthy right.
6
6
  */
7
7
  export function coalesceOr(left, right) {
8
8
  const l = truthy(left);
@@ -1,12 +1,7 @@
1
1
  import { type Val } from "./val.js";
2
2
  /**
3
- * Comparison, deliberately narrow: both sides must be concrete and of the same
4
- * primitive type.
5
- *
6
- * GitHub coerces across types when it compares, and the corner cases are
7
- * genuinely surprising (`'' == 0` is true). Every comparison that matters in
8
- * practice is string-to-string — `inputs.gates == ''`,
9
- * `github.event_name == 'pull_request'` — so modelling the coercion table
10
- * would add risk without adding reach. Mixed types return unknown.
3
+ * Both sides must be concrete and of the same primitive type. GitHub coerces
4
+ * across types and the corner cases surprise (`'' == 0` is true), so modelling
5
+ * that table would add risk without reach. Mixed types return unknown.
11
6
  */
12
7
  export declare function compare(op: string, left: Val, right: Val): Val;
@@ -1,14 +1,9 @@
1
1
  import { asBool } from "./asBool.js";
2
2
  import { UNKNOWN } from "./val.js";
3
3
  /**
4
- * Comparison, deliberately narrow: both sides must be concrete and of the same
5
- * primitive type.
6
- *
7
- * GitHub coerces across types when it compares, and the corner cases are
8
- * genuinely surprising (`'' == 0` is true). Every comparison that matters in
9
- * practice is string-to-string — `inputs.gates == ''`,
10
- * `github.event_name == 'pull_request'` — so modelling the coercion table
11
- * would add risk without adding reach. Mixed types return unknown.
4
+ * Both sides must be concrete and of the same primitive type. GitHub coerces
5
+ * across types and the corner cases surprise (`'' == 0` is true), so modelling
6
+ * that table would add risk without reach. Mixed types return unknown.
12
7
  */
13
8
  export function compare(op, left, right) {
14
9
  // GitHub compares arrays and objects by instance, and two written sides are
@@ -1,3 +1,2 @@
1
1
  import type { Scope } from "./val.js";
2
- /** Evaluate a condition to a truthiness, or null when it cannot be settled. */
3
2
  export declare function evaluate(cond: string, scope?: Scope): boolean | null;
@@ -1,6 +1,5 @@
1
1
  import { evaluateValue } from "./evaluateValue.js";
2
2
  import { truthy } from "./truthy.js";
3
- /** Evaluate a condition to a truthiness, or null when it cannot be settled. */
4
3
  export function evaluate(cond, scope = {}) {
5
4
  return truthy(evaluateValue(cond, scope));
6
5
  }
@@ -1,33 +1,12 @@
1
1
  /**
2
2
  * A tri-state evaluator for the slice of GitHub expressions that job `if:`
3
- * conditions actually use.
3
+ * conditions use. Two rules carry the module:
4
4
  *
5
- * The point is not to reimplement the expression language. It is to stop
6
- * returning `unknown` for conditions that are already decided. A reusable
7
- * workflow's jobs commonly guard on the caller's own literals:
8
- *
9
- * if: ${{ github.event_name == 'pull_request'
10
- * && (inputs.gates == '' || contains(inputs.gates, '"mutation"'))
11
- * && (needs.detect.outputs.mutation_languages || ...) != '[]' }}
12
- *
13
- * The last clause needs a job that has not run yet. The middle one does not:
14
- * the caller passed `gates` as a literal string, and it does not contain
15
- * `"mutation"`, so the clause is false, so the whole `&&` is false whatever
16
- * `detect` reports. Deciding that is the difference between a predictable
17
- * check name and a hole in the prediction.
18
- *
19
- * Two ideas carry the whole module:
20
- *
21
- * 1. **Truthiness can be known when the value is not.** `A && B` with an
22
- * unknown `A` and a false `B` is false either way. So the lattice has four
23
- * points, not three: a concrete value, known-truthy, known-falsy, and
24
- * nothing at all.
25
- *
26
- * 2. **Unrecognized is unknown, never a guess.** An unparseable condition, an
27
- * unsupported function, a comparison between types we do not model — all
28
- * collapse to `unknown` and leave the caller exactly where it was. This
29
- * adds no tolerance and no third outcome; it converts conditions that are
30
- * already decidable into the answer they already have.
5
+ * 1. Truthiness can be known when the value is not `A && B` with an unknown
6
+ * `A` and a false `B` is false either way. So the lattice has four points,
7
+ * not three: a concrete value, known-truthy, known-falsy, nothing at all.
8
+ * 2. Unrecognized is unknown, never a guess. An unparseable condition, an
9
+ * unsupported function, an unmodelled comparison all collapse to `unknown`.
31
10
  */
32
11
  export { evaluate } from "./evaluate.js";
33
12
  export { evaluateValue } from "./evaluateValue.js";
@@ -1,33 +1,12 @@
1
1
  /**
2
2
  * A tri-state evaluator for the slice of GitHub expressions that job `if:`
3
- * conditions actually use.
3
+ * conditions use. Two rules carry the module:
4
4
  *
5
- * The point is not to reimplement the expression language. It is to stop
6
- * returning `unknown` for conditions that are already decided. A reusable
7
- * workflow's jobs commonly guard on the caller's own literals:
8
- *
9
- * if: ${{ github.event_name == 'pull_request'
10
- * && (inputs.gates == '' || contains(inputs.gates, '"mutation"'))
11
- * && (needs.detect.outputs.mutation_languages || ...) != '[]' }}
12
- *
13
- * The last clause needs a job that has not run yet. The middle one does not:
14
- * the caller passed `gates` as a literal string, and it does not contain
15
- * `"mutation"`, so the clause is false, so the whole `&&` is false whatever
16
- * `detect` reports. Deciding that is the difference between a predictable
17
- * check name and a hole in the prediction.
18
- *
19
- * Two ideas carry the whole module:
20
- *
21
- * 1. **Truthiness can be known when the value is not.** `A && B` with an
22
- * unknown `A` and a false `B` is false either way. So the lattice has four
23
- * points, not three: a concrete value, known-truthy, known-falsy, and
24
- * nothing at all.
25
- *
26
- * 2. **Unrecognized is unknown, never a guess.** An unparseable condition, an
27
- * unsupported function, a comparison between types we do not model — all
28
- * collapse to `unknown` and leave the caller exactly where it was. This
29
- * adds no tolerance and no third outcome; it converts conditions that are
30
- * already decidable into the answer they already have.
5
+ * 1. Truthiness can be known when the value is not `A && B` with an unknown
6
+ * `A` and a false `B` is false either way. So the lattice has four points,
7
+ * not three: a concrete value, known-truthy, known-falsy, nothing at all.
8
+ * 2. Unrecognized is unknown, never a guess. An unparseable condition, an
9
+ * unsupported function, an unmodelled comparison all collapse to `unknown`.
31
10
  */
32
11
  export { evaluate } from "./evaluate.js";
33
12
  export { evaluateValue } from "./evaluateValue.js";
@@ -1,11 +1,8 @@
1
1
  import type { Cursor } from "./cursor.js";
2
2
  import type { Scope, Val } from "./val.js";
3
3
  /**
4
- * Recursive descent over GitHub's precedence order, loosest first:
5
- * `||`, then `&&`, then comparison, then `!`, then a primary.
6
- *
7
4
  * `&&` and `||` are value operators, not boolean ones — `a || b` yields the
8
- * first truthy operand, which is why `(x || y) != '[]'` parses as a
9
- * comparison against a coalesced value rather than a boolean.
5
+ * first truthy operand, which is why `(x || y) != '[]'` parses as a comparison
6
+ * against a coalesced value rather than a boolean.
10
7
  */
11
8
  export declare function parseOr(cur: Cursor, scope: Scope): Val;
@@ -1,12 +1,9 @@
1
1
  import { coalesceOr } from "./coalesceOr.js";
2
2
  import { parseAnd } from "./parseAnd.js";
3
3
  /**
4
- * Recursive descent over GitHub's precedence order, loosest first:
5
- * `||`, then `&&`, then comparison, then `!`, then a primary.
6
- *
7
4
  * `&&` and `||` are value operators, not boolean ones — `a || b` yields the
8
- * first truthy operand, which is why `(x || y) != '[]'` parses as a
9
- * comparison against a coalesced value rather than a boolean.
5
+ * first truthy operand, which is why `(x || y) != '[]'` parses as a comparison
6
+ * against a coalesced value rather than a boolean.
10
7
  */
11
8
  export function parseOr(cur, scope) {
12
9
  let left = parseAnd(cur, scope);
@@ -1,7 +1,6 @@
1
1
  import type { Val } from "./val.js";
2
2
  /**
3
- * GitHub's truthiness: empty string, zero, `false` and null are false, and
4
- * every other value is true. `'0'` and `'false'` are non-empty strings, so
5
- * both are true — the same trap as JavaScript, kept deliberately identical.
3
+ * GitHub's truthiness: `''`, 0, `false` and null are false. `'0'` and `'false'`
4
+ * are non-empty strings, so both are true the same trap as JavaScript.
6
5
  */
7
6
  export declare function truthy(val: Val): boolean | null;
@@ -1,7 +1,6 @@
1
1
  /**
2
- * GitHub's truthiness: empty string, zero, `false` and null are false, and
3
- * every other value is true. `'0'` and `'false'` are non-empty strings, so
4
- * both are true — the same trap as JavaScript, kept deliberately identical.
2
+ * GitHub's truthiness: `''`, 0, `false` and null are false. `'0'` and `'false'`
3
+ * are non-empty strings, so both are true the same trap as JavaScript.
5
4
  */
6
5
  export function truthy(val) {
7
6
  switch (val.kind) {
@@ -11,8 +10,8 @@ export function truthy(val) {
11
10
  return false;
12
11
  case "unknown":
13
12
  return null;
14
- // GitHub does cast an array or an object to a boolean, but no workflow
15
- // asks it to, and the answer is not worth guessing at to find out.
13
+ // GitHub does cast these, but no workflow asks it to, so guessing buys
14
+ // nothing.
16
15
  case "json":
17
16
  return null;
18
17
  case "value": {
@@ -1,10 +1,3 @@
1
1
  import type { Scope } from "../expr/val.js";
2
- /**
3
- * A scope with the fixed pull-request facts filled in.
4
- *
5
- * Every `${{ }}` this module evaluates — a job `if:`, a matrix axis — is
6
- * evaluated for the same event, so they all get the same `github.*`. Anything
7
- * the caller states wins; there is nothing here worth overriding, but a scope
8
- * that silently ignored what it was handed would be the wrong shape.
9
- */
2
+ /** Anything the caller states wins over the defaults. */
10
3
  export declare const prScope: (scope: Scope) => Scope;
@@ -1,18 +1,10 @@
1
1
  /**
2
- * The `github.*` values that are fixed for everything this module predicts.
3
- * `predict` only ever answers for a pull request, so `event_name` is not a
4
- * variable which is what lets a `github.event_name == 'pull_request'` guard
5
- * resolve instead of hanging the job on an unknown.
2
+ * `predict` only ever answers for a pull request, so `event_name` is fixed —
3
+ * which is what lets a `github.event_name == 'pull_request'` guard resolve
4
+ * instead of hanging the job on an unknown.
6
5
  */
7
6
  const PR_GITHUB_CONTEXT = { event_name: "pull_request" };
8
- /**
9
- * A scope with the fixed pull-request facts filled in.
10
- *
11
- * Every `${{ }}` this module evaluates — a job `if:`, a matrix axis — is
12
- * evaluated for the same event, so they all get the same `github.*`. Anything
13
- * the caller states wins; there is nothing here worth overriding, but a scope
14
- * that silently ignored what it was handed would be the wrong shape.
15
- */
7
+ /** Anything the caller states wins over the defaults. */
16
8
  export const prScope = (scope) => ({
17
9
  ...scope,
18
10
  github: { ...PR_GITHUB_CONTEXT, ...scope.github },
@@ -1,6 +1,6 @@
1
1
  /**
2
- * GitHub cuts a display name over 100 characters to 97 plus `...`. Verified
3
- * on a live dispatch (dirsql PR #1013): the cap applies to the leaf name
4
- * before any reusable-call prefixing, so it lives here, not on the entry.
2
+ * GitHub cuts a display name over 100 characters to 97 plus `...`. Verified on
3
+ * dirsql PR #1013: the cap applies to the leaf name before any reusable-call
4
+ * prefixing, so it lives here, not on the entry.
5
5
  */
6
6
  export declare function capDisplayName(name: string): string;
@@ -1,7 +1,7 @@
1
1
  /**
2
- * GitHub cuts a display name over 100 characters to 97 plus `...`. Verified
3
- * on a live dispatch (dirsql PR #1013): the cap applies to the leaf name
4
- * before any reusable-call prefixing, so it lives here, not on the entry.
2
+ * GitHub cuts a display name over 100 characters to 97 plus `...`. Verified on
3
+ * dirsql PR #1013: the cap applies to the leaf name before any reusable-call
4
+ * prefixing, so it lives here, not on the entry.
5
5
  */
6
6
  export function capDisplayName(name) {
7
7
  return name.length > 100 ? `${name.slice(0, 97)}...` : name;
@@ -70,7 +70,6 @@ export interface GithubClient {
70
70
  }): Promise<{
71
71
  data: GithubCommit;
72
72
  }>;
73
- /** The file at `path` as text — always the `raw` media type. */
74
73
  getContent(params: RepoParams & {
75
74
  path: string;
76
75
  ref: string;
@@ -1,6 +1,5 @@
1
- // The GitHub REST surface willfire uses: a handful of GET endpoints plus the
2
- // tarball download, over plain `fetch`. The shape mirrors the octokit subset
3
- // it replaced (#75) so call sites and their fakes carry over unchanged.
1
+ // Plain `fetch`, shaped to mirror the octokit subset it replaced (#75) so call
2
+ // sites and their fakes carry over unchanged.
4
3
  export function makeGithubClient() {
5
4
  const token = process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN;
6
5
  if (!token) {
@@ -76,9 +75,8 @@ export function makeGithubClient() {
76
75
  },
77
76
  },
78
77
  },
79
- // A short page ends the walk. A count landing exactly on a page boundary
80
- // costs one extra empty-page request, which keeps this free of Link-header
81
- // parsing.
78
+ // A short page ends the walk. An exact page boundary costs one extra empty
79
+ // request, which keeps this free of Link-header parsing.
82
80
  paginate: async (route, params) => {
83
81
  const perPage = params.per_page ?? 30;
84
82
  const all = [];
@@ -1,7 +1,4 @@
1
- /**
2
- * The node major the image ships — also the refusal boundary for `setup-node`
3
- * and `node2x` runtimes asking for any other major.
4
- */
1
+ /** Also the refusal boundary: `setup-node` and `node2x` get no other major. */
5
2
  export declare const SANDBOX_NODE_MAJOR = 24;
6
3
  export declare const DOCKERFILE = "FROM node:24-slim\nRUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates python3 && rm -rf /var/lib/apt/lists/*\n";
7
4
  export interface SandboxConfig {
@@ -1,7 +1,4 @@
1
- /**
2
- * The node major the image ships — also the refusal boundary for `setup-node`
3
- * and `node2x` runtimes asking for any other major.
4
- */
1
+ /** Also the refusal boundary: `setup-node` and `node2x` get no other major. */
5
2
  export const SANDBOX_NODE_MAJOR = 24;
6
3
  // git and python3: checkout's postcondition and the interpreters a script on
7
4
  // a GitHub-hosted runner takes for granted.
@@ -1,15 +1,12 @@
1
1
  import type { UsesTarget } from "../types.js";
2
2
  /**
3
- * Split a job-level `uses:` into the file it names and the repo it lives in.
4
- *
5
3
  * Two spellings are legal:
6
4
  *
7
5
  * `./.github/workflows/x.yml` -> the caller's repo, same commit
8
6
  * `owner/repo/.github/workflows/x.yml@ref` -> another repo, at `ref`
9
7
  *
10
- * The ref is taken from the last `@` so a branch containing a slash
11
- * (`@feature/foo`) survives. Returns null for anything elseincluding a
12
- * reference built from an expression, which we cannot evaluate and so must not
13
- * guess a fetch target for.
8
+ * The ref comes off the last `@`, so a branch with a slash survives. Anything
9
+ * else is null, including a reference built from an expression no fetch
10
+ * target to guess at.
14
11
  */
15
12
  export declare function parseUses(uses: string): UsesTarget | null;
@@ -1,16 +1,13 @@
1
1
  import { EXPRESSION_RE } from "../names/jobDisplayName.js";
2
2
  /**
3
- * Split a job-level `uses:` into the file it names and the repo it lives in.
4
- *
5
3
  * Two spellings are legal:
6
4
  *
7
5
  * `./.github/workflows/x.yml` -> the caller's repo, same commit
8
6
  * `owner/repo/.github/workflows/x.yml@ref` -> another repo, at `ref`
9
7
  *
10
- * The ref is taken from the last `@` so a branch containing a slash
11
- * (`@feature/foo`) survives. Returns null for anything elseincluding a
12
- * reference built from an expression, which we cannot evaluate and so must not
13
- * guess a fetch target for.
8
+ * The ref comes off the last `@`, so a branch with a slash survives. Anything
9
+ * else is null, including a reference built from an expression no fetch
10
+ * target to guess at.
14
11
  */
15
12
  export function parseUses(uses) {
16
13
  if (EXPRESSION_RE.test(uses)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "willfire",
3
- "version": "0.1.36",
3
+ "version": "0.1.38",
4
4
  "description": "Predict the set of CI check entries GitHub Actions will create for a pull request",
5
5
  "license": "MIT",
6
6
  "packageManager": "pnpm@10.33.0",