@elalfner/relevo 0.9.1 → 0.9.2

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "relevo",
3
3
  "displayName": "Relevo",
4
- "version": "0.9.1",
4
+ "version": "0.9.2",
5
5
  "description": "An automatic issue-to-merge workflow where an agent builds and people verify.",
6
6
  "author": {
7
7
  "name": "Elias Alfaro"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elalfner/relevo",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "Relevo: an automatic issue-to-merge workflow where an agent builds and people verify.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -670,7 +670,7 @@ export function runChecks(f) {
670
670
  if (f.protectionError) {
671
671
  r.push(warn('repo.protection', `could not read the branch rules (${f.protectionError}), so they were not checked`, 'run doctor again; if it keeps failing, check your access to the repository'));
672
672
  } else if (f.protectionUnavailable) {
673
- r.push(warn('repo.protection', `branch protection is not available on this repository (${f.protectionUnavailable})`, 'make the repository public or upgrade the plan; until then approval and merge are only enforced by the written confirmation in `ship`'));
673
+ r.push(note('repo.protection', `no branch protection is available on this plan (${f.protectionUnavailable}): approval and merge are only enforced by the written confirmation in \`ship\`; making the repository public or upgrading the plan would add it`));
674
674
  } else {
675
675
  [['base', f.config?.branches?.base ?? 'base'], ['release', f.config?.branches?.release ?? 'release']].forEach(([key, name]) => {
676
676
  const p = f.protection?.[key];
@@ -35,7 +35,17 @@ export function loadPr(gh, repo, branch) {
35
35
  return open ? JSON.parse(gh(['pr', 'view', String(open.number), '--repo', repo, '--json', PR_FIELDS])) : null;
36
36
  }
37
37
 
38
- const notFound = (err) => /404|Not Found/i.test(String(err?.stderr ?? err?.message ?? err));
38
+ const errorText = (err) => `${err?.stderr ?? ''}\n${err?.message ?? err}`;
39
+ const notFound = (err) => /404|Not Found/i.test(errorText(err));
40
+
41
+ /**
42
+ * True when GitHub refuses a branch-rules call because of the plan or the visibility of the repository (a private
43
+ * repository on the free plan has no rulesets and no protection), not because of the token or a fault of its own.
44
+ */
45
+ export function planRefusal(err) {
46
+ const text = errorText(err);
47
+ return /HTTP 403/.test(text) && /Upgrade to GitHub|make this repository public/i.test(text);
48
+ }
39
49
 
40
50
  /** True when every file that changed from `baseline` to `head` is under `openspec/`, and something changed. */
41
51
  export function changedOnlySpecs(gh, repo, baseline, head) {
@@ -62,16 +72,22 @@ export function baselineChecks(gh, repo, baseline, head) {
62
72
 
63
73
  /**
64
74
  * Whether merging into `branch` requires status checks, by a ruleset or by classic branch protection.
65
- * Returns { required: boolean, contexts: [names] }. A branch without protection requires none.
75
+ * Returns { required: boolean, contexts: [names] }. A branch without protection requires none, and neither does one
76
+ * whose rules the plan of the repository does not offer. Any other failure to read them is thrown: checks that exist
77
+ * must not be skipped because they could not be read.
66
78
  */
67
79
  export function requiredChecks(gh, repo, branch) {
68
80
  const contexts = new Set();
69
81
  let required = false;
70
- const rules = JSON.parse(gh(['api', `repos/${repo}/rules/branches/${encodeURIComponent(branch)}`]));
71
- (Array.isArray(rules) ? rules : []).filter((r) => r.type === 'required_status_checks').forEach((r) => {
72
- required = true;
73
- (r.parameters?.required_status_checks ?? []).forEach((c) => contexts.add(c.context));
74
- });
82
+ try {
83
+ const rules = JSON.parse(gh(['api', `repos/${repo}/rules/branches/${encodeURIComponent(branch)}`]));
84
+ (Array.isArray(rules) ? rules : []).filter((r) => r.type === 'required_status_checks').forEach((r) => {
85
+ required = true;
86
+ (r.parameters?.required_status_checks ?? []).forEach((c) => contexts.add(c.context));
87
+ });
88
+ } catch (err) {
89
+ if (!planRefusal(err)) throw err;
90
+ }
75
91
  try {
76
92
  const protection = JSON.parse(gh(['api', `repos/${repo}/branches/${encodeURIComponent(branch)}/protection`]));
77
93
  const classic = protection.required_status_checks;
@@ -81,7 +97,7 @@ export function requiredChecks(gh, repo, branch) {
81
97
  (classic.checks ?? []).forEach((c) => contexts.add(c.context));
82
98
  }
83
99
  } catch (err) {
84
- if (!notFound(err)) throw err;
100
+ if (!notFound(err) && !planRefusal(err)) throw err;
85
101
  }
86
102
  return { required, contexts: [...contexts] };
87
103
  }
@@ -262,8 +262,9 @@ export async function runUnattendedShip(ctx) {
262
262
  }
263
263
  }
264
264
  return { outcome: 'merged', pr: pr.number, commit };
265
- } catch (err) {
266
- if (!(err instanceof Stop)) throw err;
265
+ } catch (caught) {
266
+ // An error the run did not foresee (a refusal of GitHub, for example) stops it like any other reason, with its message.
267
+ const err = caught instanceof Stop ? caught : new Stop('ship_stopped', { reasons: `unexpected error: ${firstLines(caught?.stderr || caught?.message || caught)}` });
267
268
  removeShipLabel();
268
269
  gh(['issue', 'comment', n, '--repo', repo, '--body', renderMessage(err.kind, { actor, issue: n, ...err.data }, config.language)]);
269
270
  return { outcome: 'stopped', kind: err.kind, ...err.data };
@@ -1,4 +1,4 @@
1
- # relevo-template: 0.9.1
1
+ # relevo-template: 0.9.2
2
2
  # Relevo: posts one digest per working day, as a comment on the issue named by digest.issue in relevo.yml.
3
3
  # It lists who is waiting on what and the alerts, and posts nothing when nothing is waiting.
4
4
  # Subscribe your chat tool to that issue to receive it. Copy to .github/workflows/relevo-digest.yml.
@@ -14,7 +14,7 @@ name: relevo-digest
14
14
  # The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
15
15
  # were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
16
16
  env:
17
- RELEVO_VERSION: '0.9.1'
17
+ RELEVO_VERSION: '0.9.2'
18
18
 
19
19
  on:
20
20
  schedule:
@@ -1,4 +1,4 @@
1
- # relevo-template: 0.9.1
1
+ # relevo-template: 0.9.2
2
2
  # Relevo: builds an issue when someone with write access labels it `relevo:ready`.
3
3
  # Copy to .github/workflows/relevo-go.yml. If you renamed the ready label in relevo.yml, change it below too.
4
4
  #
@@ -20,7 +20,7 @@ name: relevo-go
20
20
  # The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
21
21
  # were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
22
22
  env:
23
- RELEVO_VERSION: '0.9.1'
23
+ RELEVO_VERSION: '0.9.2'
24
24
  # The version of OpenSpec that the job installs for itself.
25
25
  OPENSPEC_VERSION: '1.14.3'
26
26
 
@@ -1,4 +1,4 @@
1
- # relevo-template: 0.9.1
1
+ # relevo-template: 0.9.2
2
2
  # Relevo: re-evaluates the queue and starts the issues that are now eligible.
3
3
  # Copy to .github/workflows/relevo-queue.yml next to relevo-go.yml (it starts builds through that workflow).
4
4
  #
@@ -15,7 +15,7 @@ name: relevo-queue
15
15
  # The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
16
16
  # were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
17
17
  env:
18
- RELEVO_VERSION: '0.9.1'
18
+ RELEVO_VERSION: '0.9.2'
19
19
 
20
20
  on:
21
21
  issues:
@@ -1,4 +1,4 @@
1
- # relevo-template: 0.9.1
1
+ # relevo-template: 0.9.2
2
2
  # Relevo: keeps the issue label in step with its pull request.
3
3
  # A review that requests changes sets `relevo:changes-requested` and mentions the author; a new push returns the
4
4
  # issue to `relevo:pr-open`; an approval mentions the person who will run /relevo:ship.
@@ -15,7 +15,7 @@ name: relevo-review
15
15
  # The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
16
16
  # were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
17
17
  env:
18
- RELEVO_VERSION: '0.9.1'
18
+ RELEVO_VERSION: '0.9.2'
19
19
 
20
20
  on:
21
21
  pull_request_review:
@@ -1,4 +1,4 @@
1
- # relevo-template: 0.9.1
1
+ # relevo-template: 0.9.2
2
2
  # Relevo: ships an issue when a person applies the `relevo:ship` label to it.
3
3
  # Copy to .github/workflows/relevo-ship.yml. If you renamed the ship label in relevo.yml, change it below too.
4
4
  #
@@ -27,7 +27,7 @@ name: relevo-ship
27
27
  # The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
28
28
  # were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
29
29
  env:
30
- RELEVO_VERSION: '0.9.1'
30
+ RELEVO_VERSION: '0.9.2'
31
31
  # The version of OpenSpec that the job installs for itself.
32
32
  OPENSPEC_VERSION: '1.14.3'
33
33
 
@@ -44,6 +44,10 @@ permissions:
44
44
  contents: write
45
45
  issues: write
46
46
  pull-requests: write
47
+ # To read the checks of the approved head (the status check rollup of a commit); without them the run stops on
48
+ # "Resource not accessible by integration".
49
+ checks: read
50
+ statuses: read
47
51
  # To start the queue (relevo-queue.yml) after the close, which the workflow token's close does not start by itself.
48
52
  actions: write
49
53
 
@@ -1,4 +1,4 @@
1
- # relevo-template: 0.9.1
1
+ # relevo-template: 0.9.2
2
2
  # Relevo: reminds the reviewer when a pull request waits longer than limits.review_sla_hours, and escalates
3
3
  # to the spec writer (adding the needs-reviewer label) after the same time again.
4
4
  # Copy to .github/workflows/relevo-sla.yml. Needs a relevo.yml at the repository root.
@@ -13,7 +13,7 @@ name: relevo-sla
13
13
  # The version of @elalfner/relevo whose scripts this workflow runs. The plugin's templates carry the version they
14
14
  # were released with, and `/relevo:init` reports this file as different when it is older; change it by hand to update.
15
15
  env:
16
- RELEVO_VERSION: '0.9.1'
16
+ RELEVO_VERSION: '0.9.2'
17
17
 
18
18
  on:
19
19
  schedule: